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

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