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 121KB

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