You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

query_test.rb 86KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187
  1. # encoding: utf-8
  2. #
  3. # Redmine - project management software
  4. # Copyright (C) 2006-2017 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. require File.expand_path('../../test_helper', __FILE__)
  20. class QueryTest < ActiveSupport::TestCase
  21. include Redmine::I18n
  22. fixtures :projects, :enabled_modules, :users, :members,
  23. :member_roles, :roles, :trackers, :issue_statuses,
  24. :issue_categories, :enumerations, :issues,
  25. :watchers, :custom_fields, :custom_values, :versions,
  26. :queries,
  27. :projects_trackers,
  28. :custom_fields_trackers,
  29. :workflows, :journals,
  30. :attachments
  31. INTEGER_KLASS = RUBY_VERSION >= "2.4" ? Integer : Fixnum
  32. def setup
  33. User.current = nil
  34. end
  35. def test_query_with_roles_visibility_should_validate_roles
  36. set_language_if_valid 'en'
  37. query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES)
  38. assert !query.save
  39. assert_include "Roles cannot be blank", query.errors.full_messages
  40. query.role_ids = [1, 2]
  41. assert query.save
  42. end
  43. def test_changing_roles_visibility_should_clear_roles
  44. query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2])
  45. assert_equal 2, query.roles.count
  46. query.visibility = IssueQuery::VISIBILITY_PUBLIC
  47. query.save!
  48. assert_equal 0, query.roles.count
  49. end
  50. def test_available_filters_should_be_ordered
  51. set_language_if_valid 'en'
  52. query = IssueQuery.new
  53. assert_equal 0, query.available_filters.keys.index('status_id')
  54. expected_order = [
  55. "Status",
  56. "Project",
  57. "Tracker",
  58. "Priority"
  59. ]
  60. assert_equal expected_order,
  61. (query.available_filters.values.map{|v| v[:name]} & expected_order)
  62. end
  63. def test_available_filters_with_custom_fields_should_be_ordered
  64. set_language_if_valid 'en'
  65. UserCustomField.create!(
  66. :name => 'order test', :field_format => 'string',
  67. :is_for_all => true, :is_filter => true
  68. )
  69. query = IssueQuery.new
  70. expected_order = [
  71. "Searchable field",
  72. "Database",
  73. "Project's Development status",
  74. "Author's order test",
  75. "Assignee's order test"
  76. ]
  77. assert_equal expected_order,
  78. (query.available_filters.values.map{|v| v[:name]} & expected_order)
  79. end
  80. def test_custom_fields_for_all_projects_should_be_available_in_global_queries
  81. query = IssueQuery.new(:project => nil, :name => '_')
  82. assert query.available_filters.has_key?('cf_1')
  83. assert !query.available_filters.has_key?('cf_3')
  84. end
  85. def test_system_shared_versions_should_be_available_in_global_queries
  86. Version.find(2).update_attribute :sharing, 'system'
  87. query = IssueQuery.new(:project => nil, :name => '_')
  88. assert query.available_filters.has_key?('fixed_version_id')
  89. assert query.available_filters['fixed_version_id'][:values].detect {|v| v[1] == '2'}
  90. end
  91. def test_project_filter_in_global_queries
  92. query = IssueQuery.new(:project => nil, :name => '_')
  93. project_filter = query.available_filters["project_id"]
  94. assert_not_nil project_filter
  95. project_ids = project_filter[:values].map{|p| p[1]}
  96. assert project_ids.include?("1") #public project
  97. assert !project_ids.include?("2") #private project user cannot see
  98. end
  99. def test_available_filters_should_not_include_fields_disabled_on_all_trackers
  100. Tracker.all.each do |tracker|
  101. tracker.core_fields = Tracker::CORE_FIELDS - ['start_date']
  102. tracker.save!
  103. end
  104. query = IssueQuery.new(:name => '_')
  105. assert_include 'due_date', query.available_filters
  106. assert_not_include 'start_date', query.available_filters
  107. end
  108. def test_filter_values_without_project_should_be_arrays
  109. q = IssueQuery.new
  110. assert_nil q.project
  111. q.available_filters.each do |name, filter|
  112. values = filter.values
  113. assert (values.nil? || values.is_a?(Array)),
  114. "#values for #{name} filter returned a #{values.class.name}"
  115. end
  116. end
  117. def test_filter_values_with_project_should_be_arrays
  118. q = IssueQuery.new(:project => Project.find(1))
  119. assert_not_nil q.project
  120. q.available_filters.each do |name, filter|
  121. values = filter.values
  122. assert (values.nil? || values.is_a?(Array)),
  123. "#values for #{name} filter returned a #{values.class.name}"
  124. end
  125. end
  126. def find_issues_with_query(query)
  127. Issue.joins(:status, :tracker, :project, :priority).where(
  128. query.statement
  129. ).to_a
  130. end
  131. def assert_find_issues_with_query_is_successful(query)
  132. assert_nothing_raised do
  133. find_issues_with_query(query)
  134. end
  135. end
  136. def assert_query_statement_includes(query, condition)
  137. assert_include condition, query.statement
  138. end
  139. def assert_query_result(expected, query)
  140. assert_nothing_raised do
  141. assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
  142. assert_equal expected.size, query.issue_count
  143. end
  144. end
  145. def test_query_should_allow_shared_versions_for_a_project_query
  146. subproject_version = Version.find(4)
  147. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  148. filter = query.available_filters["fixed_version_id"]
  149. assert_not_nil filter
  150. assert_include subproject_version.id.to_s, filter[:values].map(&:second)
  151. end
  152. def test_query_with_multiple_custom_fields
  153. query = IssueQuery.find(1)
  154. assert query.valid?
  155. issues = find_issues_with_query(query)
  156. assert_equal 1, issues.length
  157. assert_equal Issue.find(3), issues.first
  158. end
  159. def test_operator_none
  160. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  161. query.add_filter('fixed_version_id', '!*', [''])
  162. query.add_filter('cf_1', '!*', [''])
  163. assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
  164. assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
  165. find_issues_with_query(query)
  166. end
  167. def test_operator_none_for_integer
  168. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  169. query.add_filter('estimated_hours', '!*', [''])
  170. issues = find_issues_with_query(query)
  171. assert !issues.empty?
  172. assert issues.all? {|i| !i.estimated_hours}
  173. end
  174. def test_operator_none_for_date
  175. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  176. query.add_filter('start_date', '!*', [''])
  177. issues = find_issues_with_query(query)
  178. assert !issues.empty?
  179. assert issues.all? {|i| i.start_date.nil?}
  180. end
  181. def test_operator_none_for_string_custom_field
  182. CustomField.find(2).update_attribute :default_value, ""
  183. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  184. query.add_filter('cf_2', '!*', [''])
  185. assert query.has_filter?('cf_2')
  186. issues = find_issues_with_query(query)
  187. assert !issues.empty?
  188. assert issues.all? {|i| i.custom_field_value(2).blank?}
  189. end
  190. def test_operator_none_for_text
  191. query = IssueQuery.new(:name => '_')
  192. query.add_filter('status_id', '*', [''])
  193. query.add_filter('description', '!*', [''])
  194. assert query.has_filter?('description')
  195. issues = find_issues_with_query(query)
  196. assert issues.any?
  197. assert issues.all? {|i| i.description.blank?}
  198. assert_equal [11, 12], issues.map(&:id).sort
  199. end
  200. def test_operator_all
  201. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  202. query.add_filter('fixed_version_id', '*', [''])
  203. query.add_filter('cf_1', '*', [''])
  204. assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
  205. assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
  206. find_issues_with_query(query)
  207. end
  208. def test_operator_all_for_date
  209. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  210. query.add_filter('start_date', '*', [''])
  211. issues = find_issues_with_query(query)
  212. assert !issues.empty?
  213. assert issues.all? {|i| i.start_date.present?}
  214. end
  215. def test_operator_all_for_string_custom_field
  216. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  217. query.add_filter('cf_2', '*', [''])
  218. assert query.has_filter?('cf_2')
  219. issues = find_issues_with_query(query)
  220. assert !issues.empty?
  221. assert issues.all? {|i| i.custom_field_value(2).present?}
  222. end
  223. def test_numeric_filter_should_not_accept_non_numeric_values
  224. query = IssueQuery.new(:name => '_')
  225. query.add_filter('estimated_hours', '=', ['a'])
  226. assert query.has_filter?('estimated_hours')
  227. assert !query.valid?
  228. end
  229. def test_operator_is_on_float
  230. Issue.where(:id => 2).update_all("estimated_hours = 171.2")
  231. query = IssueQuery.new(:name => '_')
  232. query.add_filter('estimated_hours', '=', ['171.20'])
  233. issues = find_issues_with_query(query)
  234. assert_equal 1, issues.size
  235. assert_equal 2, issues.first.id
  236. end
  237. def test_operator_is_on_issue_id_should_accept_comma_separated_values
  238. query = IssueQuery.new(:name => '_')
  239. query.add_filter("issue_id", '=', ['1,3'])
  240. issues = find_issues_with_query(query)
  241. assert_equal 2, issues.size
  242. assert_equal [1,3], issues.map(&:id).sort
  243. end
  244. def test_operator_between_on_issue_id_should_return_range
  245. query = IssueQuery.new(:name => '_')
  246. query.add_filter("issue_id", '><', ['2','3'])
  247. issues = find_issues_with_query(query)
  248. assert_equal 2, issues.size
  249. assert_equal [2,3], issues.map(&:id).sort
  250. end
  251. def test_operator_is_on_integer_custom_field
  252. f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
  253. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
  254. CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
  255. CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  256. query = IssueQuery.new(:name => '_')
  257. query.add_filter("cf_#{f.id}", '=', ['12'])
  258. issues = find_issues_with_query(query)
  259. assert_equal 1, issues.size
  260. assert_equal 2, issues.first.id
  261. end
  262. def test_operator_is_on_integer_custom_field_should_accept_negative_value
  263. f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
  264. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
  265. CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
  266. CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  267. query = IssueQuery.new(:name => '_')
  268. query.add_filter("cf_#{f.id}", '=', ['-12'])
  269. assert query.valid?
  270. issues = find_issues_with_query(query)
  271. assert_equal 1, issues.size
  272. assert_equal 2, issues.first.id
  273. end
  274. def test_operator_is_on_float_custom_field
  275. f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
  276. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
  277. CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
  278. CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  279. query = IssueQuery.new(:name => '_')
  280. query.add_filter("cf_#{f.id}", '=', ['12.7'])
  281. issues = find_issues_with_query(query)
  282. assert_equal 1, issues.size
  283. assert_equal 2, issues.first.id
  284. end
  285. def test_operator_is_on_float_custom_field_should_accept_negative_value
  286. f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
  287. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
  288. CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
  289. CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  290. query = IssueQuery.new(:name => '_')
  291. query.add_filter("cf_#{f.id}", '=', ['-12.7'])
  292. assert query.valid?
  293. issues = find_issues_with_query(query)
  294. assert_equal 1, issues.size
  295. assert_equal 2, issues.first.id
  296. end
  297. def test_operator_is_on_multi_list_custom_field
  298. f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
  299. :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
  300. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
  301. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
  302. CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
  303. query = IssueQuery.new(:name => '_')
  304. query.add_filter("cf_#{f.id}", '=', ['value1'])
  305. issues = find_issues_with_query(query)
  306. assert_equal [1, 3], issues.map(&:id).sort
  307. query = IssueQuery.new(:name => '_')
  308. query.add_filter("cf_#{f.id}", '=', ['value2'])
  309. issues = find_issues_with_query(query)
  310. assert_equal [1], issues.map(&:id).sort
  311. end
  312. def test_operator_is_not_on_multi_list_custom_field
  313. f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
  314. :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
  315. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
  316. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
  317. CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
  318. query = IssueQuery.new(:name => '_')
  319. query.add_filter("cf_#{f.id}", '!', ['value1'])
  320. issues = find_issues_with_query(query)
  321. assert !issues.map(&:id).include?(1)
  322. assert !issues.map(&:id).include?(3)
  323. query = IssueQuery.new(:name => '_')
  324. query.add_filter("cf_#{f.id}", '!', ['value2'])
  325. issues = find_issues_with_query(query)
  326. assert !issues.map(&:id).include?(1)
  327. assert issues.map(&:id).include?(3)
  328. end
  329. def test_operator_is_on_string_custom_field_with_utf8_value
  330. f = IssueCustomField.create!(:name => 'filter', :field_format => 'string', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
  331. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'Kiểm')
  332. query = IssueQuery.new(:name => '_')
  333. query.add_filter("cf_#{f.id}", '=', ['Kiểm'])
  334. issues = find_issues_with_query(query)
  335. assert_equal [1], issues.map(&:id).sort
  336. end
  337. def test_operator_is_on_is_private_field
  338. # is_private filter only available for those who can set issues private
  339. User.current = User.find(2)
  340. query = IssueQuery.new(:name => '_')
  341. assert query.available_filters.key?('is_private')
  342. query.add_filter("is_private", '=', ['1'])
  343. issues = find_issues_with_query(query)
  344. assert issues.any?
  345. assert_nil issues.detect {|issue| !issue.is_private?}
  346. ensure
  347. User.current = nil
  348. end
  349. def test_operator_is_not_on_is_private_field
  350. # is_private filter only available for those who can set issues private
  351. User.current = User.find(2)
  352. query = IssueQuery.new(:name => '_')
  353. assert query.available_filters.key?('is_private')
  354. query.add_filter("is_private", '!', ['1'])
  355. issues = find_issues_with_query(query)
  356. assert issues.any?
  357. assert_nil issues.detect {|issue| issue.is_private?}
  358. ensure
  359. User.current = nil
  360. end
  361. def test_operator_greater_than
  362. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  363. query.add_filter('done_ratio', '>=', ['40'])
  364. assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
  365. find_issues_with_query(query)
  366. end
  367. def test_operator_greater_than_a_float
  368. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  369. query.add_filter('estimated_hours', '>=', ['40.5'])
  370. assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
  371. find_issues_with_query(query)
  372. end
  373. def test_operator_greater_than_on_int_custom_field
  374. f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
  375. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
  376. CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
  377. CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  378. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  379. query.add_filter("cf_#{f.id}", '>=', ['8'])
  380. issues = find_issues_with_query(query)
  381. assert_equal 1, issues.size
  382. assert_equal 2, issues.first.id
  383. end
  384. def test_operator_lesser_than
  385. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  386. query.add_filter('done_ratio', '<=', ['30'])
  387. assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
  388. find_issues_with_query(query)
  389. end
  390. def test_operator_lesser_than_on_custom_field
  391. f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
  392. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  393. query.add_filter("cf_#{f.id}", '<=', ['30'])
  394. assert_match /CAST.+ <= 30\.0/, query.statement
  395. find_issues_with_query(query)
  396. end
  397. def test_operator_lesser_than_on_date_custom_field
  398. f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
  399. CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
  400. CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
  401. CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
  402. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  403. query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
  404. issue_ids = find_issues_with_query(query).map(&:id)
  405. assert_include 1, issue_ids
  406. assert_not_include 2, issue_ids
  407. assert_not_include 3, issue_ids
  408. end
  409. def test_operator_between
  410. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  411. query.add_filter('done_ratio', '><', ['30', '40'])
  412. assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
  413. find_issues_with_query(query)
  414. end
  415. def test_operator_between_on_custom_field
  416. f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
  417. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  418. query.add_filter("cf_#{f.id}", '><', ['30', '40'])
  419. assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
  420. find_issues_with_query(query)
  421. end
  422. def test_date_filter_should_not_accept_non_date_values
  423. query = IssueQuery.new(:name => '_')
  424. query.add_filter('created_on', '=', ['a'])
  425. assert query.has_filter?('created_on')
  426. assert !query.valid?
  427. end
  428. def test_date_filter_should_not_accept_invalid_date_values
  429. query = IssueQuery.new(:name => '_')
  430. query.add_filter('created_on', '=', ['2011-01-34'])
  431. assert query.has_filter?('created_on')
  432. assert !query.valid?
  433. end
  434. def test_relative_date_filter_should_not_accept_non_integer_values
  435. query = IssueQuery.new(:name => '_')
  436. query.add_filter('created_on', '>t-', ['a'])
  437. assert query.has_filter?('created_on')
  438. assert !query.valid?
  439. end
  440. def test_operator_date_equals
  441. query = IssueQuery.new(:name => '_')
  442. query.add_filter('due_date', '=', ['2011-07-10'])
  443. 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+)?/,
  444. query.statement
  445. find_issues_with_query(query)
  446. end
  447. def test_operator_date_lesser_than
  448. query = IssueQuery.new(:name => '_')
  449. query.add_filter('due_date', '<=', ['2011-07-10'])
  450. assert_match /issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/, query.statement
  451. find_issues_with_query(query)
  452. end
  453. def test_operator_date_lesser_than_with_timestamp
  454. query = IssueQuery.new(:name => '_')
  455. query.add_filter('updated_on', '<=', ['2011-07-10T19:13:52'])
  456. assert_match /issues\.updated_on <= '#{quoted_date "2011-07-10"} 19:13:52/, query.statement
  457. find_issues_with_query(query)
  458. end
  459. def test_operator_date_greater_than
  460. query = IssueQuery.new(:name => '_')
  461. query.add_filter('due_date', '>=', ['2011-07-10'])
  462. assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?'/, query.statement
  463. find_issues_with_query(query)
  464. end
  465. def test_operator_date_greater_than_with_timestamp
  466. query = IssueQuery.new(:name => '_')
  467. query.add_filter('updated_on', '>=', ['2011-07-10T19:13:52'])
  468. assert_match /issues\.updated_on > '#{quoted_date "2011-07-10"} 19:13:51(\.0+)?'/, query.statement
  469. find_issues_with_query(query)
  470. end
  471. def test_operator_date_between
  472. query = IssueQuery.new(:name => '_')
  473. query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
  474. 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+)?'/,
  475. query.statement
  476. find_issues_with_query(query)
  477. end
  478. def test_operator_in_more_than
  479. Issue.find(7).update_attribute(:due_date, (Date.today + 15))
  480. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  481. query.add_filter('due_date', '>t+', ['15'])
  482. issues = find_issues_with_query(query)
  483. assert !issues.empty?
  484. issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
  485. end
  486. def test_operator_in_less_than
  487. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  488. query.add_filter('due_date', '<t+', ['15'])
  489. issues = find_issues_with_query(query)
  490. assert !issues.empty?
  491. issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
  492. end
  493. def test_operator_in_the_next_days
  494. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  495. query.add_filter('due_date', '><t+', ['15'])
  496. issues = find_issues_with_query(query)
  497. assert !issues.empty?
  498. issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
  499. end
  500. def test_operator_less_than_ago
  501. Issue.find(7).update_attribute(:due_date, (Date.today - 3))
  502. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  503. query.add_filter('due_date', '>t-', ['3'])
  504. issues = find_issues_with_query(query)
  505. assert !issues.empty?
  506. issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
  507. end
  508. def test_operator_in_the_past_days
  509. Issue.find(7).update_attribute(:due_date, (Date.today - 3))
  510. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  511. query.add_filter('due_date', '><t-', ['3'])
  512. issues = find_issues_with_query(query)
  513. assert !issues.empty?
  514. issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
  515. end
  516. def test_operator_more_than_ago
  517. Issue.find(7).update_attribute(:due_date, (Date.today - 10))
  518. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  519. query.add_filter('due_date', '<t-', ['10'])
  520. assert query.statement.include?("#{Issue.table_name}.due_date <=")
  521. issues = find_issues_with_query(query)
  522. assert !issues.empty?
  523. issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
  524. end
  525. def test_operator_in
  526. Issue.find(7).update_attribute(:due_date, (Date.today + 2))
  527. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  528. query.add_filter('due_date', 't+', ['2'])
  529. issues = find_issues_with_query(query)
  530. assert !issues.empty?
  531. issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
  532. end
  533. def test_operator_ago
  534. Issue.find(7).update_attribute(:due_date, (Date.today - 3))
  535. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  536. query.add_filter('due_date', 't-', ['3'])
  537. issues = find_issues_with_query(query)
  538. assert !issues.empty?
  539. issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
  540. end
  541. def test_operator_today
  542. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  543. query.add_filter('due_date', 't', [''])
  544. issues = find_issues_with_query(query)
  545. assert !issues.empty?
  546. issues.each {|issue| assert_equal Date.today, issue.due_date}
  547. end
  548. def test_operator_date_periods
  549. %w(t ld w lw l2w m lm y).each do |operator|
  550. query = IssueQuery.new(:name => '_')
  551. query.add_filter('due_date', operator, [''])
  552. assert query.valid?
  553. assert query.issues
  554. end
  555. end
  556. def test_operator_datetime_periods
  557. %w(t ld w lw l2w m lm y).each do |operator|
  558. query = IssueQuery.new(:name => '_')
  559. query.add_filter('created_on', operator, [''])
  560. assert query.valid?
  561. assert query.issues
  562. end
  563. end
  564. def test_operator_contains
  565. issue = Issue.generate!(:subject => 'AbCdEfG')
  566. query = IssueQuery.new(:name => '_')
  567. query.add_filter('subject', '~', ['cdeF'])
  568. result = find_issues_with_query(query)
  569. assert_include issue, result
  570. result.each {|issue| assert issue.subject.downcase.include?('cdef') }
  571. end
  572. def test_operator_contains_with_utf8_string
  573. issue = Issue.generate!(:subject => 'Subject contains Kiểm')
  574. query = IssueQuery.new(:name => '_')
  575. query.add_filter('subject', '~', ['Kiểm'])
  576. result = find_issues_with_query(query)
  577. assert_include issue, result
  578. assert_equal 1, result.size
  579. end
  580. def test_operator_does_not_contain
  581. issue = Issue.generate!(:subject => 'AbCdEfG')
  582. query = IssueQuery.new(:name => '_')
  583. query.add_filter('subject', '!~', ['cdeF'])
  584. result = find_issues_with_query(query)
  585. assert_not_include issue, result
  586. end
  587. def test_range_for_this_week_with_week_starting_on_monday
  588. I18n.locale = :fr
  589. assert_equal '1', I18n.t(:general_first_day_of_week)
  590. Date.stubs(:today).returns(Date.parse('2011-04-29'))
  591. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  592. query.add_filter('due_date', 'w', [''])
  593. 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+)?/,
  594. query.statement
  595. I18n.locale = :en
  596. end
  597. def test_range_for_this_week_with_week_starting_on_sunday
  598. I18n.locale = :en
  599. assert_equal '7', I18n.t(:general_first_day_of_week)
  600. Date.stubs(:today).returns(Date.parse('2011-04-29'))
  601. query = IssueQuery.new(:project => Project.find(1), :name => '_')
  602. query.add_filter('due_date', 'w', [''])
  603. 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+)?/,
  604. query.statement
  605. end
  606. def test_filter_assigned_to_me
  607. user = User.find(2)
  608. group = Group.find(10)
  609. group.users << user
  610. other_group = Group.find(11)
  611. Member.create!(:project_id => 1, :principal => group, :role_ids => [1])
  612. Member.create!(:project_id => 1, :principal => other_group, :role_ids => [1])
  613. User.current = user
  614. with_settings :issue_group_assignment => '1' do
  615. i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
  616. i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
  617. i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => other_group)
  618. query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
  619. result = query.issues
  620. assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
  621. assert result.include?(i1)
  622. assert result.include?(i2)
  623. assert !result.include?(i3)
  624. end
  625. end
  626. def test_filter_updated_by
  627. user = User.generate!
  628. Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
  629. Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
  630. Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes')
  631. query = IssueQuery.new(:name => '_')
  632. filter_name = "updated_by"
  633. assert_include filter_name, query.available_filters.keys
  634. query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
  635. assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
  636. query.filters = {filter_name => {:operator => '!', :values => [user.id]}}
  637. assert_equal (Issue.ids.sort - [2, 3]), find_issues_with_query(query).map(&:id).sort
  638. end
  639. def test_filter_updated_by_should_ignore_private_notes_that_are_not_visible
  640. user = User.generate!
  641. Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true)
  642. Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
  643. query = IssueQuery.new(:name => '_')
  644. filter_name = "updated_by"
  645. assert_include filter_name, query.available_filters.keys
  646. with_current_user User.anonymous do
  647. query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
  648. assert_equal [3], find_issues_with_query(query).map(&:id).sort
  649. end
  650. end
  651. def test_filter_updated_by_me
  652. user = User.generate!
  653. Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
  654. with_current_user user do
  655. query = IssueQuery.new(:name => '_')
  656. filter_name = "updated_by"
  657. assert_include filter_name, query.available_filters.keys
  658. query.filters = {filter_name => {:operator => '=', :values => ['me']}}
  659. assert_equal [2], find_issues_with_query(query).map(&:id).sort
  660. end
  661. end
  662. def test_filter_last_updated_by
  663. user = User.generate!
  664. Journal.create!(:user_id => user.id, :journalized => Issue.find(2), :notes => 'Notes')
  665. Journal.create!(:user_id => user.id, :journalized => Issue.find(3), :notes => 'Notes')
  666. Journal.create!(:user_id => 2, :journalized => Issue.find(3), :notes => 'Notes')
  667. query = IssueQuery.new(:name => '_')
  668. filter_name = "last_updated_by"
  669. assert_include filter_name, query.available_filters.keys
  670. query.filters = {filter_name => {:operator => '=', :values => [user.id]}}
  671. assert_equal [2], find_issues_with_query(query).map(&:id).sort
  672. end
  673. def test_filter_last_updated_by_should_ignore_private_notes_that_are_not_visible
  674. user1 = User.generate!
  675. user2 = User.generate!
  676. Journal.create!(:user_id => user1.id, :journalized => Issue.find(2), :notes => 'Notes')
  677. Journal.create!(:user_id => user2.id, :journalized => Issue.find(2), :notes => 'Notes', :private_notes => true)
  678. query = IssueQuery.new(:name => '_')
  679. filter_name = "last_updated_by"
  680. assert_include filter_name, query.available_filters.keys
  681. with_current_user User.anonymous do
  682. query.filters = {filter_name => {:operator => '=', :values => [user1.id]}}
  683. assert_equal [2], find_issues_with_query(query).map(&:id).sort
  684. query.filters = {filter_name => {:operator => '=', :values => [user2.id]}}
  685. assert_equal [], find_issues_with_query(query).map(&:id).sort
  686. end
  687. with_current_user User.find(2) do
  688. query.filters = {filter_name => {:operator => '=', :values => [user1.id]}}
  689. assert_equal [], find_issues_with_query(query).map(&:id).sort
  690. query.filters = {filter_name => {:operator => '=', :values => [user2.id]}}
  691. assert_equal [2], find_issues_with_query(query).map(&:id).sort
  692. end
  693. end
  694. def test_user_custom_field_filtered_on_me
  695. User.current = User.find(2)
  696. cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
  697. issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
  698. issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
  699. query = IssueQuery.new(:name => '_', :project => Project.find(1))
  700. filter = query.available_filters["cf_#{cf.id}"]
  701. assert_not_nil filter
  702. assert_include 'me', filter[:values].map{|v| v[1]}
  703. query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
  704. result = query.issues
  705. assert_equal 1, result.size
  706. assert_equal issue1, result.first
  707. end
  708. def test_filter_on_me_by_anonymous_user
  709. User.current = nil
  710. query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
  711. assert_equal [], query.issues
  712. end
  713. def test_filter_my_projects
  714. User.current = User.find(2)
  715. query = IssueQuery.new(:name => '_')
  716. filter = query.available_filters['project_id']
  717. assert_not_nil filter
  718. assert_include 'mine', filter[:values].map{|v| v[1]}
  719. query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
  720. result = query.issues
  721. assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
  722. end
  723. def test_filter_watched_issues
  724. User.current = User.find(1)
  725. query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
  726. result = find_issues_with_query(query)
  727. assert_not_nil result
  728. assert !result.empty?
  729. assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
  730. User.current = nil
  731. end
  732. def test_filter_unwatched_issues
  733. User.current = User.find(1)
  734. query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
  735. result = find_issues_with_query(query)
  736. assert_not_nil result
  737. assert !result.empty?
  738. assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
  739. User.current = nil
  740. end
  741. def test_filter_on_watched_issues_with_view_issue_watchers_permission
  742. User.current = User.find(1)
  743. User.current.admin = true
  744. assert User.current.allowed_to?(:view_issue_watchers, Project.find(1))
  745. Issue.find(1).add_watcher User.current
  746. Issue.find(3).add_watcher User.find(3)
  747. query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me', '3']}})
  748. result = find_issues_with_query(query)
  749. assert_includes result, Issue.find(1)
  750. assert_includes result, Issue.find(3)
  751. ensure
  752. User.current.reload
  753. User.current = nil
  754. end
  755. def test_filter_on_watched_issues_without_view_issue_watchers_permission
  756. User.current = User.find(1)
  757. User.current.admin = false
  758. assert !User.current.allowed_to?(:view_issue_watchers, Project.find(1))
  759. Issue.find(1).add_watcher User.current
  760. Issue.find(3).add_watcher User.find(3)
  761. query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me', '3']}})
  762. result = find_issues_with_query(query)
  763. assert_includes result, Issue.find(1)
  764. assert_not_includes result, Issue.find(3)
  765. ensure
  766. User.current.reload
  767. User.current = nil
  768. end
  769. def test_filter_on_custom_field_should_ignore_projects_with_field_disabled
  770. field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_for_all => false, :is_filter => true)
  771. Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
  772. Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
  773. query = IssueQuery.new(:name => '_', :project => Project.find(1))
  774. query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
  775. assert_equal 2, find_issues_with_query(query).size
  776. field.project_ids = [1, 3] # Disable the field for project 4
  777. field.save!
  778. assert_equal 1, find_issues_with_query(query).size
  779. end
  780. def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled
  781. field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true)
  782. Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'})
  783. Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
  784. query = IssueQuery.new(:name => '_', :project => Project.find(1))
  785. query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
  786. assert_equal 2, find_issues_with_query(query).size
  787. field.tracker_ids = [1] # Disable the field for tracker 2
  788. field.save!
  789. assert_equal 1, find_issues_with_query(query).size
  790. end
  791. def test_filter_on_project_custom_field
  792. field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
  793. CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
  794. CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
  795. query = IssueQuery.new(:name => '_')
  796. filter_name = "project.cf_#{field.id}"
  797. assert_include filter_name, query.available_filters.keys
  798. query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
  799. assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
  800. end
  801. def test_filter_on_author_custom_field
  802. field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
  803. CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
  804. query = IssueQuery.new(:name => '_')
  805. filter_name = "author.cf_#{field.id}"
  806. assert_include filter_name, query.available_filters.keys
  807. query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
  808. assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
  809. end
  810. def test_filter_on_assigned_to_custom_field
  811. field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
  812. CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
  813. query = IssueQuery.new(:name => '_')
  814. filter_name = "assigned_to.cf_#{field.id}"
  815. assert_include filter_name, query.available_filters.keys
  816. query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
  817. assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
  818. end
  819. def test_filter_on_fixed_version_custom_field
  820. field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
  821. CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
  822. query = IssueQuery.new(:name => '_')
  823. filter_name = "fixed_version.cf_#{field.id}"
  824. assert_include filter_name, query.available_filters.keys
  825. query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
  826. assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
  827. end
  828. def test_filter_on_fixed_version_due_date
  829. query = IssueQuery.new(:name => '_')
  830. filter_name = "fixed_version.due_date"
  831. assert_include filter_name, query.available_filters.keys
  832. query.filters = {filter_name => {:operator => '=', :values => [20.day.from_now.to_date.to_s(:db)]}}
  833. issues = find_issues_with_query(query)
  834. assert_equal [2], issues.map(&:fixed_version_id).uniq.sort
  835. assert_equal [2, 12], issues.map(&:id).sort
  836. query = IssueQuery.new(:name => '_')
  837. query.filters = {filter_name => {:operator => '>=', :values => [21.day.from_now.to_date.to_s(:db)]}}
  838. assert_equal 0, find_issues_with_query(query).size
  839. end
  840. def test_filter_on_fixed_version_status
  841. query = IssueQuery.new(:name => '_')
  842. filter_name = "fixed_version.status"
  843. assert_include filter_name, query.available_filters.keys
  844. query.filters = {filter_name => {:operator => '=', :values => ['closed']}}
  845. issues = find_issues_with_query(query)
  846. assert_equal [1], issues.map(&:fixed_version_id).sort
  847. assert_equal [11], issues.map(&:id).sort
  848. # "is not" operator should include issues without target version
  849. query = IssueQuery.new(:name => '_')
  850. query.filters = {filter_name => {:operator => '!', :values => ['open', 'closed', 'locked']}, "project_id" => {:operator => '=', :values => [1]}}
  851. assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort
  852. end
  853. def test_filter_on_version_custom_field
  854. field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
  855. issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => '2'})
  856. query = IssueQuery.new(:name => '_')
  857. filter_name = "cf_#{field.id}"
  858. assert_include filter_name, query.available_filters.keys
  859. query.filters = {filter_name => {:operator => '=', :values => ['2']}}
  860. issues = find_issues_with_query(query)
  861. assert_equal [issue.id], issues.map(&:id).sort
  862. end
  863. def test_filter_on_attribute_of_version_custom_field
  864. field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
  865. version = Version.generate!(:effective_date => '2017-01-14')
  866. issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s})
  867. query = IssueQuery.new(:name => '_')
  868. filter_name = "cf_#{field.id}.due_date"
  869. assert_include filter_name, query.available_filters.keys
  870. query.filters = {filter_name => {:operator => '=', :values => ['2017-01-14']}}
  871. issues = find_issues_with_query(query)
  872. assert_equal [issue.id], issues.map(&:id).sort
  873. end
  874. def test_filter_on_custom_field_of_version_custom_field
  875. field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
  876. attr = VersionCustomField.generate!(:field_format => 'string', :is_filter => true)
  877. version = Version.generate!(:custom_field_values => {attr.id.to_s => 'ABC'})
  878. issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s})
  879. query = IssueQuery.new(:name => '_')
  880. filter_name = "cf_#{field.id}.cf_#{attr.id}"
  881. assert_include filter_name, query.available_filters.keys
  882. query.filters = {filter_name => {:operator => '=', :values => ['ABC']}}
  883. issues = find_issues_with_query(query)
  884. assert_equal [issue.id], issues.map(&:id).sort
  885. end
  886. def test_filter_on_relations_with_a_specific_issue
  887. IssueRelation.delete_all
  888. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
  889. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
  890. query = IssueQuery.new(:name => '_')
  891. query.filters = {"relates" => {:operator => '=', :values => ['1']}}
  892. assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
  893. query = IssueQuery.new(:name => '_')
  894. query.filters = {"relates" => {:operator => '=', :values => ['2']}}
  895. assert_equal [1], find_issues_with_query(query).map(&:id).sort
  896. end
  897. def test_filter_on_relations_with_any_issues_in_a_project
  898. IssueRelation.delete_all
  899. with_settings :cross_project_issue_relations => '1' do
  900. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
  901. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
  902. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
  903. end
  904. query = IssueQuery.new(:name => '_')
  905. query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
  906. assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
  907. query = IssueQuery.new(:name => '_')
  908. query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
  909. assert_equal [1], find_issues_with_query(query).map(&:id).sort
  910. query = IssueQuery.new(:name => '_')
  911. query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
  912. assert_equal [], find_issues_with_query(query).map(&:id).sort
  913. end
  914. def test_filter_on_relations_with_any_issues_not_in_a_project
  915. IssueRelation.delete_all
  916. with_settings :cross_project_issue_relations => '1' do
  917. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
  918. #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
  919. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
  920. end
  921. query = IssueQuery.new(:name => '_')
  922. query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
  923. assert_equal [1], find_issues_with_query(query).map(&:id).sort
  924. end
  925. def test_filter_on_relations_with_no_issues_in_a_project
  926. IssueRelation.delete_all
  927. with_settings :cross_project_issue_relations => '1' do
  928. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
  929. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
  930. IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
  931. end
  932. query = IssueQuery.new(:name => '_')
  933. query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
  934. ids = find_issues_with_query(query).map(&:id).sort
  935. assert_include 2, ids
  936. assert_not_include 1, ids
  937. assert_not_include 3, ids
  938. end
  939. def test_filter_on_relations_with_any_open_issues
  940. IssueRelation.delete_all
  941. # Issue 1 is blocked by 8, which is closed
  942. IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
  943. # Issue 2 is blocked by 3, which is open
  944. IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
  945. query = IssueQuery.new(:name => '_')
  946. query.filters = {"blocked" => {:operator => "*o", :values => ['']}}
  947. ids = find_issues_with_query(query).map(&:id)
  948. assert_equal [], ids & [1]
  949. assert_include 2, ids
  950. end
  951. def test_filter_on_blocked_by_no_open_issues
  952. IssueRelation.delete_all
  953. # Issue 1 is blocked by 8, which is closed
  954. IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
  955. # Issue 2 is blocked by 3, which is open
  956. IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
  957. query = IssueQuery.new(:name => '_')
  958. query.filters = {"blocked" => {:operator => "!o", :values => ['']}}
  959. ids = find_issues_with_query(query).map(&:id)
  960. assert_equal [], ids & [2]
  961. assert_include 1, ids
  962. end
  963. def test_filter_on_related_with_no_open_issues
  964. IssueRelation.delete_all
  965. # Issue 1 is blocked by 8, which is closed
  966. IssueRelation.create!(relation_type: 'relates', issue_from: Issue.find(1), issue_to: Issue.find(8))
  967. # Issue 2 is blocked by 3, which is open
  968. IssueRelation.create!(relation_type: 'relates', issue_from: Issue.find(2), issue_to: Issue.find(3))
  969. query = IssueQuery.new(:name => '_')
  970. query.filters = { 'relates' => { operator: '!o', values: [''] } }
  971. ids = find_issues_with_query(query).map(&:id)
  972. assert_equal [], ids & [2]
  973. assert_include 1, ids
  974. end
  975. def test_filter_on_relations_with_no_issues
  976. IssueRelation.delete_all
  977. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
  978. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
  979. query = IssueQuery.new(:name => '_')
  980. query.filters = {"relates" => {:operator => '!*', :values => ['']}}
  981. ids = find_issues_with_query(query).map(&:id)
  982. assert_equal [], ids & [1, 2, 3]
  983. assert_include 4, ids
  984. end
  985. def test_filter_on_relations_with_any_issues
  986. IssueRelation.delete_all
  987. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
  988. IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
  989. query = IssueQuery.new(:name => '_')
  990. query.filters = {"relates" => {:operator => '*', :values => ['']}}
  991. assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
  992. end
  993. def test_filter_on_relations_should_not_ignore_other_filter
  994. issue = Issue.generate!
  995. issue1 = Issue.generate!(:status_id => 1)
  996. issue2 = Issue.generate!(:status_id => 2)
  997. IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
  998. IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)
  999. query = IssueQuery.new(:name => '_')
  1000. query.filters = {
  1001. "status_id" => {:operator => '=', :values => ['1']},
  1002. "relates" => {:operator => '=', :values => [issue.id.to_s]}
  1003. }
  1004. assert_equal [issue1], find_issues_with_query(query)
  1005. end
  1006. def test_filter_on_parent
  1007. Issue.delete_all
  1008. parent = Issue.generate_with_descendants!
  1009. query = IssueQuery.new(:name => '_')
  1010. query.filters = {"parent_id" => {:operator => '=', :values => [parent.id.to_s]}}
  1011. assert_equal parent.children.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
  1012. query.filters = {"parent_id" => {:operator => '~', :values => [parent.id.to_s]}}
  1013. assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
  1014. query.filters = {"parent_id" => {:operator => '*', :values => ['']}}
  1015. assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
  1016. query.filters = {"parent_id" => {:operator => '!*', :values => ['']}}
  1017. assert_equal [parent.id], find_issues_with_query(query).map(&:id).sort
  1018. end
  1019. def test_filter_on_invalid_parent_should_return_no_results
  1020. query = IssueQuery.new(:name => '_')
  1021. query.filters = {"parent_id" => {:operator => '=', :values => '99999999999'}}
  1022. assert_equal [], find_issues_with_query(query).map(&:id).sort
  1023. query.filters = {"parent_id" => {:operator => '~', :values => '99999999999'}}
  1024. assert_equal [], find_issues_with_query(query)
  1025. end
  1026. def test_filter_on_child
  1027. Issue.delete_all
  1028. parent = Issue.generate_with_descendants!
  1029. child, leaf = parent.children.sort_by(&:id)
  1030. grandchild = child.children.first
  1031. query = IssueQuery.new(:name => '_')
  1032. query.filters = {"child_id" => {:operator => '=', :values => [grandchild.id.to_s]}}
  1033. assert_equal [child.id], find_issues_with_query(query).map(&:id).sort
  1034. query.filters = {"child_id" => {:operator => '~', :values => [grandchild.id.to_s]}}
  1035. assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
  1036. query.filters = {"child_id" => {:operator => '*', :values => ['']}}
  1037. assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
  1038. query.filters = {"child_id" => {:operator => '!*', :values => ['']}}
  1039. assert_equal [grandchild, leaf].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
  1040. end
  1041. def test_filter_on_invalid_child_should_return_no_results
  1042. query = IssueQuery.new(:name => '_')
  1043. query.filters = {"child_id" => {:operator => '=', :values => '99999999999'}}
  1044. assert_equal [], find_issues_with_query(query)
  1045. query.filters = {"child_id" => {:operator => '~', :values => '99999999999'}}
  1046. assert_equal [].map(&:id).sort, find_issues_with_query(query)
  1047. end
  1048. def test_filter_on_attachment_any
  1049. query = IssueQuery.new(:name => '_')
  1050. query.filters = {"attachment" => {:operator => '*', :values => ['']}}
  1051. issues = find_issues_with_query(query)
  1052. assert issues.any?
  1053. assert_nil issues.detect {|issue| issue.attachments.empty?}
  1054. end
  1055. def test_filter_on_attachment_none
  1056. query = IssueQuery.new(:name => '_')
  1057. query.filters = {"attachment" => {:operator => '!*', :values => ['']}}
  1058. issues = find_issues_with_query(query)
  1059. assert issues.any?
  1060. assert_nil issues.detect {|issue| issue.attachments.any?}
  1061. end
  1062. def test_filter_on_attachment_contains
  1063. query = IssueQuery.new(:name => '_')
  1064. query.filters = {"attachment" => {:operator => '~', :values => ['error281']}}
  1065. issues = find_issues_with_query(query)
  1066. assert issues.any?
  1067. assert_nil issues.detect {|issue| ! issue.attachments.any? {|attachment| attachment.filename.include?('error281')}}
  1068. end
  1069. def test_filter_on_attachment_not_contains
  1070. query = IssueQuery.new(:name => '_')
  1071. query.filters = {"attachment" => {:operator => '!~', :values => ['error281']}}
  1072. issues = find_issues_with_query(query)
  1073. assert issues.any?
  1074. assert_nil issues.detect {|issue| issue.attachments.any? {|attachment| attachment.filename.include?('error281')}}
  1075. end
  1076. def test_statement_should_be_nil_with_no_filters
  1077. q = IssueQuery.new(:name => '_')
  1078. q.filters = {}
  1079. assert q.valid?
  1080. assert_nil q.statement
  1081. end
  1082. def test_available_filters_as_json_should_include_missing_assigned_to_id_values
  1083. user = User.generate!
  1084. with_current_user User.find(1) do
  1085. q = IssueQuery.new
  1086. q.filters = {"assigned_to_id" => {:operator => '=', :values => user.id.to_s}}
  1087. filters = q.available_filters_as_json
  1088. assert_include [user.name, user.id.to_s], filters['assigned_to_id']['values']
  1089. end
  1090. end
  1091. def test_available_filters_as_json_should_include_missing_author_id_values
  1092. user = User.generate!
  1093. with_current_user User.find(1) do
  1094. q = IssueQuery.new
  1095. q.filters = {"author_id" => {:operator => '=', :values => user.id.to_s}}
  1096. filters = q.available_filters_as_json
  1097. assert_include [user.name, user.id.to_s], filters['author_id']['values']
  1098. end
  1099. end
  1100. def test_default_columns
  1101. q = IssueQuery.new
  1102. assert q.columns.any?
  1103. assert q.inline_columns.any?
  1104. assert q.block_columns.empty?
  1105. end
  1106. def test_set_column_names
  1107. q = IssueQuery.new
  1108. q.column_names = ['tracker', :subject, '', 'unknonw_column']
  1109. assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
  1110. end
  1111. def test_has_column_should_accept_a_column_name
  1112. q = IssueQuery.new
  1113. q.column_names = ['tracker', :subject]
  1114. assert q.has_column?(:tracker)
  1115. assert !q.has_column?(:category)
  1116. end
  1117. def test_has_column_should_accept_a_column
  1118. q = IssueQuery.new
  1119. q.column_names = ['tracker', :subject]
  1120. tracker_column = q.available_columns.detect {|c| c.name==:tracker}
  1121. assert_kind_of QueryColumn, tracker_column
  1122. category_column = q.available_columns.detect {|c| c.name==:category}
  1123. assert_kind_of QueryColumn, category_column
  1124. assert q.has_column?(tracker_column)
  1125. assert !q.has_column?(category_column)
  1126. end
  1127. def test_has_column_should_return_true_for_default_column
  1128. with_settings :issue_list_default_columns => %w(tracker subject) do
  1129. q = IssueQuery.new
  1130. assert q.has_column?(:tracker)
  1131. assert !q.has_column?(:category)
  1132. end
  1133. end
  1134. def test_inline_and_block_columns
  1135. q = IssueQuery.new
  1136. q.column_names = ['subject', 'description', 'tracker', 'last_notes']
  1137. assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
  1138. assert_equal [:description, :last_notes], q.block_columns.map(&:name)
  1139. end
  1140. def test_custom_field_columns_should_be_inline
  1141. q = IssueQuery.new
  1142. columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
  1143. assert columns.any?
  1144. assert_nil columns.detect {|column| !column.inline?}
  1145. end
  1146. def test_query_should_preload_spent_hours
  1147. q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
  1148. assert q.has_column?(:spent_hours)
  1149. issues = q.issues
  1150. assert_not_nil issues.first.instance_variable_get("@spent_hours")
  1151. end
  1152. def test_query_should_preload_last_updated_by
  1153. with_current_user User.find(2) do
  1154. q = IssueQuery.new(:name => '_', :column_names => [:subject, :last_updated_by])
  1155. q.filters = {"issue_id" => {:operator => '=', :values => ['1,2,3']}}
  1156. assert q.has_column?(:last_updated_by)
  1157. issues = q.issues.sort_by(&:id)
  1158. assert issues.all? {|issue| !issue.instance_variable_get("@last_updated_by").nil?}
  1159. assert_equal ["User", "User", "NilClass"], issues.map { |i| i.last_updated_by.class.name}
  1160. assert_equal ["John Smith", "John Smith", ""], issues.map { |i| i.last_updated_by.to_s }
  1161. end
  1162. end
  1163. def test_query_should_preload_last_notes
  1164. q = IssueQuery.new(:name => '_', :column_names => [:subject, :last_notes])
  1165. assert q.has_column?(:last_notes)
  1166. issues = q.issues
  1167. assert_not_nil issues.first.instance_variable_get("@last_notes")
  1168. end
  1169. def test_groupable_columns_should_include_custom_fields
  1170. q = IssueQuery.new
  1171. column = q.groupable_columns.detect {|c| c.name == :cf_1}
  1172. assert_not_nil column
  1173. assert_kind_of QueryCustomFieldColumn, column
  1174. end
  1175. def test_groupable_columns_should_not_include_multi_custom_fields
  1176. field = CustomField.find(1)
  1177. field.update_attribute :multiple, true
  1178. q = IssueQuery.new
  1179. column = q.groupable_columns.detect {|c| c.name == :cf_1}
  1180. assert_nil column
  1181. end
  1182. def test_groupable_columns_should_include_user_custom_fields
  1183. cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
  1184. q = IssueQuery.new
  1185. assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
  1186. end
  1187. def test_groupable_columns_should_include_version_custom_fields
  1188. cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
  1189. q = IssueQuery.new
  1190. assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
  1191. end
  1192. def test_grouped_with_valid_column
  1193. q = IssueQuery.new(:group_by => 'status')
  1194. assert q.grouped?
  1195. assert_not_nil q.group_by_column
  1196. assert_equal :status, q.group_by_column.name
  1197. assert_not_nil q.group_by_statement
  1198. assert_equal 'status', q.group_by_statement
  1199. end
  1200. def test_grouped_with_invalid_column
  1201. q = IssueQuery.new(:group_by => 'foo')
  1202. assert !q.grouped?
  1203. assert_nil q.group_by_column
  1204. assert_nil q.group_by_statement
  1205. end
  1206. def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
  1207. with_settings :user_format => 'lastname_comma_firstname' do
  1208. q = IssueQuery.new
  1209. assert q.sortable_columns.has_key?('assigned_to')
  1210. assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
  1211. end
  1212. end
  1213. def test_sortable_columns_should_sort_authors_according_to_user_format_setting
  1214. with_settings :user_format => 'lastname_comma_firstname' do
  1215. q = IssueQuery.new
  1216. assert q.sortable_columns.has_key?('author')
  1217. assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
  1218. end
  1219. end
  1220. def test_sortable_columns_should_sort_last_updated_by_according_to_user_format_setting
  1221. with_settings :user_format => 'lastname_comma_firstname' do
  1222. q = IssueQuery.new
  1223. q.sort_criteria = [['last_updated_by', 'desc']]
  1224. assert q.sortable_columns.has_key?('last_updated_by')
  1225. assert_equal %w(last_journal_user.lastname last_journal_user.firstname last_journal_user.id), q.sortable_columns['last_updated_by']
  1226. end
  1227. end
  1228. def test_sortable_columns_should_include_custom_field
  1229. q = IssueQuery.new
  1230. assert q.sortable_columns['cf_1']
  1231. end
  1232. def test_sortable_columns_should_not_include_multi_custom_field
  1233. field = CustomField.find(1)
  1234. field.update_attribute :multiple, true
  1235. q = IssueQuery.new
  1236. assert !q.sortable_columns['cf_1']
  1237. end
  1238. def test_default_sort
  1239. q = IssueQuery.new
  1240. assert_equal [['id', 'desc']], q.sort_criteria
  1241. end
  1242. def test_sort_criteria_should_have_only_first_three_elements
  1243. q = IssueQuery.new
  1244. q.sort_criteria = [['priority', 'desc'], ['tracker', 'asc'], ['priority', 'asc'], ['id', 'asc'], ['project', 'asc'], ['subject', 'asc']]
  1245. assert_equal [['priority', 'desc'], ['tracker', 'asc'], ['priority', 'asc']], q.sort_criteria
  1246. end
  1247. def test_set_sort_criteria_with_hash
  1248. q = IssueQuery.new
  1249. q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
  1250. assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
  1251. end
  1252. def test_set_sort_criteria_with_array
  1253. q = IssueQuery.new
  1254. q.sort_criteria = [['priority', 'desc'], 'tracker']
  1255. assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
  1256. end
  1257. def test_create_query_with_sort
  1258. q = IssueQuery.new(:name => 'Sorted')
  1259. q.sort_criteria = [['priority', 'desc'], 'tracker']
  1260. assert q.save
  1261. q.reload
  1262. assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
  1263. end
  1264. def test_sort_by_string_custom_field_asc
  1265. q = IssueQuery.new
  1266. c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
  1267. assert c
  1268. assert c.sortable
  1269. q.sort_criteria = [[c.name.to_s, 'asc']]
  1270. issues = q.issues
  1271. values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
  1272. assert !values.empty?
  1273. assert_equal values.sort, values
  1274. end
  1275. def test_sort_by_string_custom_field_desc
  1276. q = IssueQuery.new
  1277. c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
  1278. assert c
  1279. assert c.sortable
  1280. q.sort_criteria = [[c.name.to_s, 'desc']]
  1281. issues = q.issues
  1282. values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
  1283. assert !values.empty?
  1284. assert_equal values.sort.reverse, values
  1285. end
  1286. def test_sort_by_float_custom_field_asc
  1287. q = IssueQuery.new
  1288. c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
  1289. assert c
  1290. assert c.sortable
  1291. q.sort_criteria = [[c.name.to_s, 'asc']]
  1292. issues = q.issues
  1293. values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
  1294. assert !values.empty?
  1295. assert_equal values.sort, values
  1296. end
  1297. def test_set_totalable_names
  1298. q = IssueQuery.new
  1299. q.totalable_names = ['estimated_hours', :spent_hours, '']
  1300. assert_equal [:estimated_hours, :spent_hours], q.totalable_columns.map(&:name)
  1301. end
  1302. def test_totalable_columns_should_default_to_settings
  1303. with_settings :issue_list_default_totals => ['estimated_hours'] do
  1304. q = IssueQuery.new
  1305. assert_equal [:estimated_hours], q.totalable_columns.map(&:name)
  1306. end
  1307. end
  1308. def test_available_totalable_columns_should_include_estimated_hours
  1309. q = IssueQuery.new
  1310. assert_include :estimated_hours, q.available_totalable_columns.map(&:name)
  1311. end
  1312. def test_available_totalable_columns_should_include_spent_hours
  1313. User.current = User.find(1)
  1314. q = IssueQuery.new
  1315. assert_include :spent_hours, q.available_totalable_columns.map(&:name)
  1316. end
  1317. def test_available_totalable_columns_should_include_int_custom_field
  1318. field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
  1319. q = IssueQuery.new
  1320. assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
  1321. end
  1322. def test_available_totalable_columns_should_include_float_custom_field
  1323. field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
  1324. q = IssueQuery.new
  1325. assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
  1326. end
  1327. def test_total_for_estimated_hours
  1328. Issue.delete_all
  1329. Issue.generate!(:estimated_hours => 5.5)
  1330. Issue.generate!(:estimated_hours => 1.1)
  1331. Issue.generate!
  1332. q = IssueQuery.new
  1333. assert_equal 6.6, q.total_for(:estimated_hours)
  1334. end
  1335. def test_total_by_group_for_estimated_hours
  1336. Issue.delete_all
  1337. Issue.generate!(:estimated_hours => 5.5, :assigned_to_id => 2)
  1338. Issue.generate!(:estimated_hours => 1.1, :assigned_to_id => 3)
  1339. Issue.generate!(:estimated_hours => 3.5)
  1340. q = IssueQuery.new(:group_by => 'assigned_to')
  1341. assert_equal(
  1342. {nil => 3.5, User.find(2) => 5.5, User.find(3) => 1.1},
  1343. q.total_by_group_for(:estimated_hours)
  1344. )
  1345. end
  1346. def test_total_for_spent_hours
  1347. TimeEntry.delete_all
  1348. TimeEntry.generate!(:hours => 5.5)
  1349. TimeEntry.generate!(:hours => 1.1)
  1350. q = IssueQuery.new
  1351. assert_equal 6.6, q.total_for(:spent_hours)
  1352. end
  1353. def test_total_by_group_for_spent_hours
  1354. TimeEntry.delete_all
  1355. TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
  1356. TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
  1357. Issue.where(:id => 1).update_all(:assigned_to_id => 2)
  1358. Issue.where(:id => 2).update_all(:assigned_to_id => 3)
  1359. q = IssueQuery.new(:group_by => 'assigned_to')
  1360. assert_equal(
  1361. {User.find(2) => 5.5, User.find(3) => 1.1},
  1362. q.total_by_group_for(:spent_hours)
  1363. )
  1364. end
  1365. def test_total_by_project_group_for_spent_hours
  1366. TimeEntry.delete_all
  1367. TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
  1368. TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
  1369. Issue.where(:id => 1).update_all(:assigned_to_id => 2)
  1370. Issue.where(:id => 2).update_all(:assigned_to_id => 3)
  1371. q = IssueQuery.new(:group_by => 'project')
  1372. assert_equal(
  1373. {Project.find(1) => 6.6},
  1374. q.total_by_group_for(:spent_hours)
  1375. )
  1376. end
  1377. def test_total_for_int_custom_field
  1378. field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
  1379. CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
  1380. CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
  1381. CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
  1382. q = IssueQuery.new
  1383. assert_equal 9, q.total_for("cf_#{field.id}")
  1384. end
  1385. def test_total_by_group_for_int_custom_field
  1386. field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
  1387. CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
  1388. CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
  1389. Issue.where(:id => 1).update_all(:assigned_to_id => 2)
  1390. Issue.where(:id => 2).update_all(:assigned_to_id => 3)
  1391. q = IssueQuery.new(:group_by => 'assigned_to')
  1392. assert_equal(
  1393. {User.find(2) => 2, User.find(3) => 7},
  1394. q.total_by_group_for("cf_#{field.id}")
  1395. )
  1396. end
  1397. def test_total_for_float_custom_field
  1398. field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
  1399. CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2.3')
  1400. CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
  1401. CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
  1402. q = IssueQuery.new
  1403. assert_equal 9.3, q.total_for("cf_#{field.id}")
  1404. end
  1405. def test_invalid_query_should_raise_query_statement_invalid_error
  1406. q = IssueQuery.new
  1407. assert_raise Query::StatementInvalid do
  1408. q.issues(:conditions => "foo = 1")
  1409. end
  1410. end
  1411. def test_issue_count
  1412. q = IssueQuery.new(:name => '_')
  1413. issue_count = q.issue_count
  1414. assert_equal q.issues.size, issue_count
  1415. end
  1416. def test_issue_count_with_archived_issues
  1417. p = Project.generate! do |project|
  1418. project.status = Project::STATUS_ARCHIVED
  1419. end
  1420. i = Issue.generate!( :project => p, :tracker => p.trackers.first )
  1421. assert !i.visible?
  1422. test_issue_count
  1423. end
  1424. def test_issue_count_by_association_group
  1425. q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
  1426. count_by_group = q.result_count_by_group
  1427. assert_kind_of Hash, count_by_group
  1428. assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
  1429. assert_equal %W(#{INTEGER_KLASS}), count_by_group.values.collect {|k| k.class.name}.uniq
  1430. assert count_by_group.has_key?(User.find(3))
  1431. end
  1432. def test_issue_count_by_list_custom_field_group
  1433. q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
  1434. count_by_group = q.result_count_by_group
  1435. assert_kind_of Hash, count_by_group
  1436. assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
  1437. assert_equal %W(#{INTEGER_KLASS}), count_by_group.values.collect {|k| k.class.name}.uniq
  1438. assert count_by_group.has_key?('MySQL')
  1439. end
  1440. def test_issue_count_by_date_custom_field_group
  1441. q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
  1442. count_by_group = q.result_count_by_group
  1443. assert_kind_of Hash, count_by_group
  1444. assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
  1445. assert_equal %W(#{INTEGER_KLASS}), count_by_group.values.collect {|k| k.class.name}.uniq
  1446. end
  1447. def test_issue_count_with_nil_group_only
  1448. Issue.update_all("assigned_to_id = NULL")
  1449. q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
  1450. count_by_group = q.result_count_by_group
  1451. assert_kind_of Hash, count_by_group
  1452. assert_equal 1, count_by_group.keys.size
  1453. assert_nil count_by_group.keys.first
  1454. end
  1455. def test_issue_ids
  1456. q = IssueQuery.new(:name => '_')
  1457. q.sort_criteria = ['subject', 'id']
  1458. issues = q.issues
  1459. assert_equal issues.map(&:id), q.issue_ids
  1460. end
  1461. def test_label_for
  1462. set_language_if_valid 'en'
  1463. q = IssueQuery.new
  1464. assert_equal 'Assignee', q.label_for('assigned_to_id')
  1465. end
  1466. def test_label_for_fr
  1467. set_language_if_valid 'fr'
  1468. q = IssueQuery.new
  1469. assert_equal "Assign\xc3\xa9 \xc3\xa0".force_encoding('UTF-8'), q.label_for('assigned_to_id')
  1470. end
  1471. def test_editable_by
  1472. admin = User.find(1)
  1473. manager = User.find(2)
  1474. developer = User.find(3)
  1475. # Public query on project 1
  1476. q = IssueQuery.find(1)
  1477. assert q.editable_by?(admin)
  1478. assert q.editable_by?(manager)
  1479. assert !q.editable_by?(developer)
  1480. # Private query on project 1
  1481. q = IssueQuery.find(2)
  1482. assert q.editable_by?(admin)
  1483. assert !q.editable_by?(manager)
  1484. assert q.editable_by?(developer)
  1485. # Private query for all projects
  1486. q = IssueQuery.find(3)
  1487. assert q.editable_by?(admin)
  1488. assert !q.editable_by?(manager)
  1489. assert q.editable_by?(developer)
  1490. end
  1491. def test_editable_by_for_global_query
  1492. admin = User.find(1)
  1493. manager = User.find(2)
  1494. developer = User.find(3)
  1495. q = IssueQuery.find(4)
  1496. assert q.editable_by?(admin)
  1497. assert !q.editable_by?(manager)
  1498. assert !q.editable_by?(developer)
  1499. end
  1500. def test_editable_by_for_global_query_with_project_set
  1501. admin = User.find(1)
  1502. manager = User.find(2)
  1503. developer = User.find(3)
  1504. q = IssueQuery.find(4)
  1505. q.project = Project.find(1)
  1506. assert q.editable_by?(admin)
  1507. assert !q.editable_by?(manager)
  1508. assert !q.editable_by?(developer)
  1509. end
  1510. def test_visible_scope
  1511. query_ids = IssueQuery.visible(User.anonymous).map(&:id)
  1512. assert query_ids.include?(1), 'public query on public project was not visible'
  1513. assert query_ids.include?(4), 'public query for all projects was not visible'
  1514. assert !query_ids.include?(2), 'private query on public project was visible'
  1515. assert !query_ids.include?(3), 'private query for all projects was visible'
  1516. assert !query_ids.include?(7), 'public query on private project was visible'
  1517. end
  1518. def test_query_with_public_visibility_should_be_visible_to_anyone
  1519. q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC)
  1520. assert q.visible?(User.anonymous)
  1521. assert IssueQuery.visible(User.anonymous).find_by_id(q.id)
  1522. assert q.visible?(User.find(7))
  1523. assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
  1524. assert q.visible?(User.find(2))
  1525. assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
  1526. assert q.visible?(User.find(1))
  1527. assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
  1528. end
  1529. def test_query_with_roles_visibility_should_be_visible_to_user_with_role
  1530. q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2])
  1531. assert !q.visible?(User.anonymous)
  1532. assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
  1533. assert !q.visible?(User.find(7))
  1534. assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id)
  1535. assert q.visible?(User.find(2))
  1536. assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
  1537. assert q.visible?(User.find(1))
  1538. assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
  1539. # Should ignore archived project memberships
  1540. Project.find(1).archive
  1541. assert !q.visible?(User.find(3))
  1542. assert_nil IssueQuery.visible(User.find(3)).find_by_id(q.id)
  1543. end
  1544. def test_query_with_private_visibility_should_be_visible_to_owner
  1545. q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7))
  1546. assert !q.visible?(User.anonymous)
  1547. assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
  1548. assert q.visible?(User.find(7))
  1549. assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
  1550. assert !q.visible?(User.find(2))
  1551. assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id)
  1552. assert q.visible?(User.find(1))
  1553. assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id)
  1554. end
  1555. test "#available_filters should include users of visible projects in cross-project view" do
  1556. users = IssueQuery.new.available_filters["assigned_to_id"]
  1557. assert_not_nil users
  1558. assert users[:values].map{|u|u[1]}.include?("3")
  1559. end
  1560. test "#available_filters should include users of subprojects" do
  1561. user1 = User.generate!
  1562. user2 = User.generate!
  1563. project = Project.find(1)
  1564. Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
  1565. users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
  1566. assert_not_nil users
  1567. assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
  1568. assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
  1569. end
  1570. test "#available_filters should include visible projects in cross-project view" do
  1571. projects = IssueQuery.new.available_filters["project_id"]
  1572. assert_not_nil projects
  1573. assert projects[:values].map{|u|u[1]}.include?("1")
  1574. end
  1575. test "#available_filters should include 'member_of_group' filter" do
  1576. query = IssueQuery.new
  1577. assert query.available_filters.keys.include?("member_of_group")
  1578. assert_equal :list_optional, query.available_filters["member_of_group"][:type]
  1579. assert query.available_filters["member_of_group"][:values].present?
  1580. assert_equal Group.givable.sort.map {|g| [g.name, g.id.to_s]},
  1581. query.available_filters["member_of_group"][:values].sort
  1582. end
  1583. test "#available_filters should include 'assigned_to_role' filter" do
  1584. query = IssueQuery.new
  1585. assert query.available_filters.keys.include?("assigned_to_role")
  1586. assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
  1587. assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
  1588. assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
  1589. assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
  1590. assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
  1591. assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
  1592. end
  1593. def test_available_filters_should_include_custom_field_according_to_user_visibility
  1594. visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
  1595. hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
  1596. with_current_user User.find(3) do
  1597. query = IssueQuery.new
  1598. assert_include "cf_#{visible_field.id}", query.available_filters.keys
  1599. assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys
  1600. end
  1601. end
  1602. def test_available_columns_should_include_custom_field_according_to_user_visibility
  1603. visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
  1604. hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
  1605. with_current_user User.find(3) do
  1606. query = IssueQuery.new
  1607. assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name)
  1608. assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name)
  1609. end
  1610. end
  1611. def setup_member_of_group
  1612. Group.destroy_all # No fixtures
  1613. @user_in_group = User.generate!
  1614. @second_user_in_group = User.generate!
  1615. @user_in_group2 = User.generate!
  1616. @user_not_in_group = User.generate!
  1617. @group = Group.generate!.reload
  1618. @group.users << @user_in_group
  1619. @group.users << @second_user_in_group
  1620. @group2 = Group.generate!.reload
  1621. @group2.users << @user_in_group2
  1622. @query = IssueQuery.new(:name => '_')
  1623. end
  1624. test "member_of_group filter should search assigned to for users in the group" do
  1625. setup_member_of_group
  1626. @query.add_filter('member_of_group', '=', [@group.id.to_s])
  1627. assert_find_issues_with_query_is_successful @query
  1628. end
  1629. test "member_of_group filter should search not assigned to any group member (none)" do
  1630. setup_member_of_group
  1631. @query.add_filter('member_of_group', '!*', [''])
  1632. assert_find_issues_with_query_is_successful @query
  1633. end
  1634. test "member_of_group filter should search assigned to any group member (all)" do
  1635. setup_member_of_group
  1636. @query.add_filter('member_of_group', '*', [''])
  1637. assert_find_issues_with_query_is_successful @query
  1638. end
  1639. test "member_of_group filter should return an empty set with = empty group" do
  1640. setup_member_of_group
  1641. @empty_group = Group.generate!
  1642. @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
  1643. assert_equal [], find_issues_with_query(@query)
  1644. end
  1645. test "member_of_group filter should return issues with ! empty group" do
  1646. setup_member_of_group
  1647. @empty_group = Group.generate!
  1648. @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
  1649. assert_find_issues_with_query_is_successful @query
  1650. end
  1651. def setup_assigned_to_role
  1652. @manager_role = Role.find_by_name('Manager')
  1653. @developer_role = Role.find_by_name('Developer')
  1654. @project = Project.generate!
  1655. @manager = User.generate!
  1656. @developer = User.generate!
  1657. @boss = User.generate!
  1658. @guest = User.generate!
  1659. User.add_to_project(@manager, @project, @manager_role)
  1660. User.add_to_project(@developer, @project, @developer_role)
  1661. User.add_to_project(@boss, @project, [@manager_role, @developer_role])
  1662. @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
  1663. @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
  1664. @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
  1665. @issue4 = Issue.generate!(:project => @project, :author_id => @guest.id, :assigned_to_id => @guest.id)
  1666. @issue5 = Issue.generate!(:project => @project)
  1667. @query = IssueQuery.new(:name => '_', :project => @project)
  1668. end
  1669. test "assigned_to_role filter should search assigned to for users with the Role" do
  1670. setup_assigned_to_role
  1671. @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
  1672. assert_query_result [@issue1, @issue3], @query
  1673. end
  1674. test "assigned_to_role filter should search assigned to for users with the Role on the issue project" do
  1675. setup_assigned_to_role
  1676. other_project = Project.generate!
  1677. User.add_to_project(@developer, other_project, @manager_role)
  1678. @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
  1679. assert_query_result [@issue1, @issue3], @query
  1680. end
  1681. test "assigned_to_role filter should return an empty set with empty role" do
  1682. setup_assigned_to_role
  1683. @empty_role = Role.generate!
  1684. @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
  1685. assert_query_result [], @query
  1686. end
  1687. test "assigned_to_role filter should search assigned to for users without the Role" do
  1688. setup_assigned_to_role
  1689. @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
  1690. assert_query_result [@issue2, @issue4, @issue5], @query
  1691. end
  1692. test "assigned_to_role filter should search assigned to for users not assigned to any Role (none)" do
  1693. setup_assigned_to_role
  1694. @query.add_filter('assigned_to_role', '!*', [''])
  1695. assert_query_result [@issue4, @issue5], @query
  1696. end
  1697. test "assigned_to_role filter should search assigned to for users assigned to any Role (all)" do
  1698. setup_assigned_to_role
  1699. @query.add_filter('assigned_to_role', '*', [''])
  1700. assert_query_result [@issue1, @issue2, @issue3], @query
  1701. end
  1702. test "assigned_to_role filter should return issues with ! empty role" do
  1703. setup_assigned_to_role
  1704. @empty_role = Role.generate!
  1705. @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
  1706. assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
  1707. end
  1708. def test_query_column_should_accept_a_symbol_as_caption
  1709. set_language_if_valid 'en'
  1710. c = QueryColumn.new('foo', :caption => :general_text_Yes)
  1711. assert_equal 'Yes', c.caption
  1712. end
  1713. def test_query_column_should_accept_a_proc_as_caption
  1714. c = QueryColumn.new('foo', :caption => lambda {'Foo'})
  1715. assert_equal 'Foo', c.caption
  1716. end
  1717. def test_date_clause_should_respect_user_time_zone_with_local_default
  1718. @query = IssueQuery.new(:name => '_')
  1719. # user is in Hawaii (-10)
  1720. User.current = users(:users_001)
  1721. User.current.pref.update_attribute :time_zone, 'Hawaii'
  1722. # assume timestamps are stored in server local time
  1723. local_zone = Time.zone
  1724. from = Date.parse '2016-03-20'
  1725. to = Date.parse '2016-03-22'
  1726. assert c = @query.send(:date_clause, 'table', 'field', from, to, false)
  1727. # the dates should have been interpreted in the user's time zone and
  1728. # converted to local time
  1729. # what we get exactly in the sql depends on the local time zone, therefore
  1730. # it's computed here.
  1731. f = User.current.time_zone.local(from.year, from.month, from.day).yesterday.end_of_day.in_time_zone(local_zone)
  1732. t = User.current.time_zone.local(to.year, to.month, to.day).end_of_day.in_time_zone(local_zone)
  1733. assert_equal "table.field > '#{Query.connection.quoted_date f}' AND table.field <= '#{Query.connection.quoted_date t}'", c
  1734. end
  1735. def test_date_clause_should_respect_user_time_zone_with_utc_default
  1736. @query = IssueQuery.new(:name => '_')
  1737. # user is in Hawaii (-10)
  1738. User.current = users(:users_001)
  1739. User.current.pref.update_attribute :time_zone, 'Hawaii'
  1740. # assume timestamps are stored as utc
  1741. ActiveRecord::Base.default_timezone = :utc
  1742. from = Date.parse '2016-03-20'
  1743. to = Date.parse '2016-03-22'
  1744. assert c = @query.send(:date_clause, 'table', 'field', from, to, false)
  1745. # the dates should have been interpreted in the user's time zone and
  1746. # converted to utc. March 20 in Hawaii begins at 10am UTC.
  1747. f = Time.new(2016, 3, 20, 9, 59, 59, 0).end_of_hour
  1748. t = Time.new(2016, 3, 23, 9, 59, 59, 0).end_of_hour
  1749. assert_equal "table.field > '#{Query.connection.quoted_date f}' AND table.field <= '#{Query.connection.quoted_date t}'", c
  1750. ensure
  1751. ActiveRecord::Base.default_timezone = :local # restore Redmine default
  1752. end
  1753. def test_filter_on_subprojects
  1754. query = IssueQuery.new(:name => '_', :project => Project.find(1))
  1755. filter_name = "subproject_id"
  1756. assert_include filter_name, query.available_filters.keys
  1757. # "is" operator should include issues of parent project + issues of the selected subproject
  1758. query.filters = {filter_name => {:operator => '=', :values => ['3']}}
  1759. issues = find_issues_with_query(query)
  1760. assert_equal [1, 2, 3, 5, 7, 8, 11, 12, 13, 14], issues.map(&:id).sort
  1761. # "is not" operator should include issues of parent project + issues of all active subprojects - issues of the selected subprojects
  1762. query = IssueQuery.new(:name => '_', :project => Project.find(1))
  1763. query.filters = {filter_name => {:operator => '!', :values => ['3']}}
  1764. issues = find_issues_with_query(query)
  1765. assert_equal [1, 2, 3, 6, 7, 8, 9, 10, 11, 12], issues.map(&:id).sort
  1766. end
  1767. def test_filter_updated_on_none_should_return_issues_with_updated_on_equal_with_created_on
  1768. query = IssueQuery.new(:name => '_', :project => Project.find(1))
  1769. query.filters = {'updated_on' => {:operator => '!*', :values => ['']}}
  1770. issues = find_issues_with_query(query)
  1771. assert_equal [3, 6, 7, 8, 9, 10, 14], issues.map(&:id).sort
  1772. end
  1773. def test_filter_updated_on_any_should_return_issues_with_updated_on_greater_than_created_on
  1774. query = IssueQuery.new(:name => '_', :project => Project.find(1))
  1775. query.filters = {'updated_on' => {:operator => '*', :values => ['']}}
  1776. issues = find_issues_with_query(query)
  1777. assert_equal [1, 2, 5, 11, 12, 13], issues.map(&:id).sort
  1778. end
  1779. def test_issue_statuses_should_return_only_statuses_used_by_that_project
  1780. query = IssueQuery.new(:name => '_', :project => Project.find(1))
  1781. query.filters = {'status_id' => {:operator => '=', :values => []}}
  1782. WorkflowTransition.delete_all
  1783. WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
  1784. WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
  1785. WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
  1786. WorkflowTransition.create(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 3)
  1787. assert_equal ['1','2','3','4'], query.available_filters['status_id'][:values].map(&:second)
  1788. end
  1789. def test_issue_statuses_without_project_should_return_all_statuses
  1790. query = IssueQuery.new(:name => '_')
  1791. query.filters = {'status_id' => {:operator => '=', :values => []}}
  1792. WorkflowTransition.delete_all
  1793. WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
  1794. WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
  1795. WorkflowTransition.create(:role_id => 1, :tracker_id => 1, :old_status_id => 2, :new_status_id => 3)
  1796. WorkflowTransition.create(:role_id => 1, :tracker_id => 2, :old_status_id => 1, :new_status_id => 3)
  1797. assert_equal ['1','2','3','4','5','6'], query.available_filters['status_id'][:values].map(&:second)
  1798. end
  1799. end