##// END OF EJS Templates
Fixed shot filter expression parsing depending upon field operators (#8371)....
Etienne Massip -
r7504:a118c4ccb093
parent child
Show More
@@ -1,787 +1,790
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @caption_key = options[:caption] || "field_#{name}"
30 @caption_key = options[:caption] || "field_#{name}"
31 end
31 end
32
32
33 def caption
33 def caption
34 l(@caption_key)
34 l(@caption_key)
35 end
35 end
36
36
37 # Returns true if the column is sortable, otherwise false
37 # Returns true if the column is sortable, otherwise false
38 def sortable?
38 def sortable?
39 !sortable.nil?
39 !sortable.nil?
40 end
40 end
41
41
42 def value(issue)
42 def value(issue)
43 issue.send name
43 issue.send name
44 end
44 end
45
45
46 def css_classes
46 def css_classes
47 name
47 name
48 end
48 end
49 end
49 end
50
50
51 class QueryCustomFieldColumn < QueryColumn
51 class QueryCustomFieldColumn < QueryColumn
52
52
53 def initialize(custom_field)
53 def initialize(custom_field)
54 self.name = "cf_#{custom_field.id}".to_sym
54 self.name = "cf_#{custom_field.id}".to_sym
55 self.sortable = custom_field.order_statement || false
55 self.sortable = custom_field.order_statement || false
56 if %w(list date bool int).include?(custom_field.field_format)
56 if %w(list date bool int).include?(custom_field.field_format)
57 self.groupable = custom_field.order_statement
57 self.groupable = custom_field.order_statement
58 end
58 end
59 self.groupable ||= false
59 self.groupable ||= false
60 @cf = custom_field
60 @cf = custom_field
61 end
61 end
62
62
63 def caption
63 def caption
64 @cf.name
64 @cf.name
65 end
65 end
66
66
67 def custom_field
67 def custom_field
68 @cf
68 @cf
69 end
69 end
70
70
71 def value(issue)
71 def value(issue)
72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
73 cv && @cf.cast_value(cv.value)
73 cv && @cf.cast_value(cv.value)
74 end
74 end
75
75
76 def css_classes
76 def css_classes
77 @css_classes ||= "#{name} #{@cf.field_format}"
77 @css_classes ||= "#{name} #{@cf.field_format}"
78 end
78 end
79 end
79 end
80
80
81 class Query < ActiveRecord::Base
81 class Query < ActiveRecord::Base
82 class StatementInvalid < ::ActiveRecord::StatementInvalid
82 class StatementInvalid < ::ActiveRecord::StatementInvalid
83 end
83 end
84
84
85 belongs_to :project
85 belongs_to :project
86 belongs_to :user
86 belongs_to :user
87 serialize :filters
87 serialize :filters
88 serialize :column_names
88 serialize :column_names
89 serialize :sort_criteria, Array
89 serialize :sort_criteria, Array
90
90
91 attr_protected :project_id, :user_id
91 attr_protected :project_id, :user_id
92
92
93 validates_presence_of :name, :on => :save
93 validates_presence_of :name, :on => :save
94 validates_length_of :name, :maximum => 255
94 validates_length_of :name, :maximum => 255
95 validate :validate_query_filters
95 validate :validate_query_filters
96
96
97 @@operators = { "=" => :label_equals,
97 @@operators = { "=" => :label_equals,
98 "!" => :label_not_equals,
98 "!" => :label_not_equals,
99 "o" => :label_open_issues,
99 "o" => :label_open_issues,
100 "c" => :label_closed_issues,
100 "c" => :label_closed_issues,
101 "!*" => :label_none,
101 "!*" => :label_none,
102 "*" => :label_all,
102 "*" => :label_all,
103 ">=" => :label_greater_or_equal,
103 ">=" => :label_greater_or_equal,
104 "<=" => :label_less_or_equal,
104 "<=" => :label_less_or_equal,
105 "><" => :label_between,
105 "><" => :label_between,
106 "<t+" => :label_in_less_than,
106 "<t+" => :label_in_less_than,
107 ">t+" => :label_in_more_than,
107 ">t+" => :label_in_more_than,
108 "t+" => :label_in,
108 "t+" => :label_in,
109 "t" => :label_today,
109 "t" => :label_today,
110 "w" => :label_this_week,
110 "w" => :label_this_week,
111 ">t-" => :label_less_than_ago,
111 ">t-" => :label_less_than_ago,
112 "<t-" => :label_more_than_ago,
112 "<t-" => :label_more_than_ago,
113 "t-" => :label_ago,
113 "t-" => :label_ago,
114 "~" => :label_contains,
114 "~" => :label_contains,
115 "!~" => :label_not_contains }
115 "!~" => :label_not_contains }
116
116
117 cattr_reader :operators
117 cattr_reader :operators
118
118
119 @@operators_by_filter_type = { :list => [ "=", "!" ],
119 @@operators_by_filter_type = { :list => [ "=", "!" ],
120 :list_status => [ "o", "=", "!", "c", "*" ],
120 :list_status => [ "o", "=", "!", "c", "*" ],
121 :list_optional => [ "=", "!", "!*", "*" ],
121 :list_optional => [ "=", "!", "!*", "*" ],
122 :list_subprojects => [ "*", "!*", "=" ],
122 :list_subprojects => [ "*", "!*", "=" ],
123 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
123 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
124 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w" ],
124 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w" ],
125 :string => [ "=", "~", "!", "!~" ],
125 :string => [ "=", "~", "!", "!~" ],
126 :text => [ "~", "!~" ],
126 :text => [ "~", "!~" ],
127 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
127 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
128 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
128 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
129
129
130 cattr_reader :operators_by_filter_type
130 cattr_reader :operators_by_filter_type
131
131
132 @@available_columns = [
132 @@available_columns = [
133 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
133 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
134 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
134 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
135 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
135 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
136 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
136 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
137 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
137 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
138 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
138 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
139 QueryColumn.new(:author),
139 QueryColumn.new(:author),
140 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
140 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
141 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
141 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
142 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
142 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
143 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
143 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
144 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
144 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
145 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
145 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
146 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
146 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
147 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
147 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
148 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
148 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
149 ]
149 ]
150 cattr_reader :available_columns
150 cattr_reader :available_columns
151
151
152 named_scope :visible, lambda {|*args|
152 named_scope :visible, lambda {|*args|
153 user = args.shift || User.current
153 user = args.shift || User.current
154 base = Project.allowed_to_condition(user, :view_issues, *args)
154 base = Project.allowed_to_condition(user, :view_issues, *args)
155 user_id = user.logged? ? user.id : 0
155 user_id = user.logged? ? user.id : 0
156 {
156 {
157 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
157 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
158 :include => :project
158 :include => :project
159 }
159 }
160 }
160 }
161
161
162 def initialize(attributes = nil)
162 def initialize(attributes = nil)
163 super attributes
163 super attributes
164 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
164 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
165 end
165 end
166
166
167 def after_initialize
167 def after_initialize
168 # Store the fact that project is nil (used in #editable_by?)
168 # Store the fact that project is nil (used in #editable_by?)
169 @is_for_all = project.nil?
169 @is_for_all = project.nil?
170 end
170 end
171
171
172 def validate_query_filters
172 def validate_query_filters
173 filters.each_key do |field|
173 filters.each_key do |field|
174 if values_for(field)
174 if values_for(field)
175 case type_for(field)
175 case type_for(field)
176 when :integer
176 when :integer
177 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
177 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
178 when :float
178 when :float
179 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+(\.\d*)?$/) }
179 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+(\.\d*)?$/) }
180 when :date, :date_past
180 when :date, :date_past
181 case operator_for(field)
181 case operator_for(field)
182 when "=", ">=", "<=", "><"
182 when "=", ">=", "<=", "><"
183 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
183 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
184 when ">t-", "<t-", "t-"
184 when ">t-", "<t-", "t-"
185 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
185 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
186 end
186 end
187 end
187 end
188 end
188 end
189
189
190 errors.add label_for(field), :blank unless
190 errors.add label_for(field), :blank unless
191 # filter requires one or more values
191 # filter requires one or more values
192 (values_for(field) and !values_for(field).first.blank?) or
192 (values_for(field) and !values_for(field).first.blank?) or
193 # filter doesn't require any value
193 # filter doesn't require any value
194 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
194 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
195 end if filters
195 end if filters
196 end
196 end
197
197
198 # Returns true if the query is visible to +user+ or the current user.
198 # Returns true if the query is visible to +user+ or the current user.
199 def visible?(user=User.current)
199 def visible?(user=User.current)
200 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
200 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
201 end
201 end
202
202
203 def editable_by?(user)
203 def editable_by?(user)
204 return false unless user
204 return false unless user
205 # Admin can edit them all and regular users can edit their private queries
205 # Admin can edit them all and regular users can edit their private queries
206 return true if user.admin? || (!is_public && self.user_id == user.id)
206 return true if user.admin? || (!is_public && self.user_id == user.id)
207 # Members can not edit public queries that are for all project (only admin is allowed to)
207 # Members can not edit public queries that are for all project (only admin is allowed to)
208 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
208 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
209 end
209 end
210
210
211 def available_filters
211 def available_filters
212 return @available_filters if @available_filters
212 return @available_filters if @available_filters
213
213
214 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
214 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
215
215
216 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
216 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
217 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
217 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
218 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
218 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
219 "subject" => { :type => :text, :order => 8 },
219 "subject" => { :type => :text, :order => 8 },
220 "created_on" => { :type => :date_past, :order => 9 },
220 "created_on" => { :type => :date_past, :order => 9 },
221 "updated_on" => { :type => :date_past, :order => 10 },
221 "updated_on" => { :type => :date_past, :order => 10 },
222 "start_date" => { :type => :date, :order => 11 },
222 "start_date" => { :type => :date, :order => 11 },
223 "due_date" => { :type => :date, :order => 12 },
223 "due_date" => { :type => :date, :order => 12 },
224 "estimated_hours" => { :type => :float, :order => 13 },
224 "estimated_hours" => { :type => :float, :order => 13 },
225 "done_ratio" => { :type => :integer, :order => 14 }}
225 "done_ratio" => { :type => :integer, :order => 14 }}
226
226
227 principals = []
227 principals = []
228 if project
228 if project
229 principals += project.principals.sort
229 principals += project.principals.sort
230 else
230 else
231 all_projects = Project.visible.all
231 all_projects = Project.visible.all
232 if all_projects.any?
232 if all_projects.any?
233 # members of visible projects
233 # members of visible projects
234 principals += Principal.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort
234 principals += Principal.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort
235
235
236 # project filter
236 # project filter
237 project_values = []
237 project_values = []
238 Project.project_tree(all_projects) do |p, level|
238 Project.project_tree(all_projects) do |p, level|
239 prefix = (level > 0 ? ('--' * level + ' ') : '')
239 prefix = (level > 0 ? ('--' * level + ' ') : '')
240 project_values << ["#{prefix}#{p.name}", p.id.to_s]
240 project_values << ["#{prefix}#{p.name}", p.id.to_s]
241 end
241 end
242 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
242 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
243 end
243 end
244 end
244 end
245 users = principals.select {|p| p.is_a?(User)}
245 users = principals.select {|p| p.is_a?(User)}
246
246
247 assigned_to_values = []
247 assigned_to_values = []
248 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
248 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
249 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
249 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
250 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
250 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
251
251
252 author_values = []
252 author_values = []
253 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
253 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
254 author_values += users.collect{|s| [s.name, s.id.to_s] }
254 author_values += users.collect{|s| [s.name, s.id.to_s] }
255 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
255 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
256
256
257 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
257 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
258 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
258 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
259
259
260 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
260 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
261 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
261 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
262
262
263 if User.current.logged?
263 if User.current.logged?
264 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
264 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
265 end
265 end
266
266
267 if project
267 if project
268 # project specific filters
268 # project specific filters
269 categories = @project.issue_categories.all
269 categories = @project.issue_categories.all
270 unless categories.empty?
270 unless categories.empty?
271 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
271 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
272 end
272 end
273 versions = @project.shared_versions.all
273 versions = @project.shared_versions.all
274 unless versions.empty?
274 unless versions.empty?
275 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
275 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
276 end
276 end
277 unless @project.leaf?
277 unless @project.leaf?
278 subprojects = @project.descendants.visible.all
278 subprojects = @project.descendants.visible.all
279 unless subprojects.empty?
279 unless subprojects.empty?
280 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
280 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
281 end
281 end
282 end
282 end
283 add_custom_fields_filters(@project.all_issue_custom_fields)
283 add_custom_fields_filters(@project.all_issue_custom_fields)
284 else
284 else
285 # global filters for cross project issue list
285 # global filters for cross project issue list
286 system_shared_versions = Version.visible.find_all_by_sharing('system')
286 system_shared_versions = Version.visible.find_all_by_sharing('system')
287 unless system_shared_versions.empty?
287 unless system_shared_versions.empty?
288 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
288 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
289 end
289 end
290 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
290 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
291 end
291 end
292 @available_filters
292 @available_filters
293 end
293 end
294
294
295 def add_filter(field, operator, values)
295 def add_filter(field, operator, values)
296 # values must be an array
296 # values must be an array
297 return unless values.nil? || values.is_a?(Array)
297 return unless values.nil? || values.is_a?(Array)
298 # check if field is defined as an available filter
298 # check if field is defined as an available filter
299 if available_filters.has_key? field
299 if available_filters.has_key? field
300 filter_options = available_filters[field]
300 filter_options = available_filters[field]
301 # check if operator is allowed for that filter
301 # check if operator is allowed for that filter
302 #if @@operators_by_filter_type[filter_options[:type]].include? operator
302 #if @@operators_by_filter_type[filter_options[:type]].include? operator
303 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
303 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
304 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
304 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
305 #end
305 #end
306 filters[field] = {:operator => operator, :values => (values || [''])}
306 filters[field] = {:operator => operator, :values => (values || [''])}
307 end
307 end
308 end
308 end
309
309
310 def add_short_filter(field, expression)
310 def add_short_filter(field, expression)
311 return unless expression
311 return unless expression && available_filters.has_key?(field)
312 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
312 field_type = available_filters[field][:type]
313 add_filter field, (parms[0] || "="), [parms[1] || ""]
313 @@operators_by_filter_type[field_type].sort.reverse.detect do |operator|
314 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
315 add_filter field, operator, $1.present? ? $1.split('|') : ['']
316 end || add_filter(field, '=', expression.split('|'))
314 end
317 end
315
318
316 # Add multiple filters using +add_filter+
319 # Add multiple filters using +add_filter+
317 def add_filters(fields, operators, values)
320 def add_filters(fields, operators, values)
318 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
321 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
319 fields.each do |field|
322 fields.each do |field|
320 add_filter(field, operators[field], values && values[field])
323 add_filter(field, operators[field], values && values[field])
321 end
324 end
322 end
325 end
323 end
326 end
324
327
325 def has_filter?(field)
328 def has_filter?(field)
326 filters and filters[field]
329 filters and filters[field]
327 end
330 end
328
331
329 def type_for(field)
332 def type_for(field)
330 available_filters[field][:type] if available_filters.has_key?(field)
333 available_filters[field][:type] if available_filters.has_key?(field)
331 end
334 end
332
335
333 def operator_for(field)
336 def operator_for(field)
334 has_filter?(field) ? filters[field][:operator] : nil
337 has_filter?(field) ? filters[field][:operator] : nil
335 end
338 end
336
339
337 def values_for(field)
340 def values_for(field)
338 has_filter?(field) ? filters[field][:values] : nil
341 has_filter?(field) ? filters[field][:values] : nil
339 end
342 end
340
343
341 def value_for(field, index=0)
344 def value_for(field, index=0)
342 (values_for(field) || [])[index]
345 (values_for(field) || [])[index]
343 end
346 end
344
347
345 def label_for(field)
348 def label_for(field)
346 label = available_filters[field][:name] if available_filters.has_key?(field)
349 label = available_filters[field][:name] if available_filters.has_key?(field)
347 label ||= field.gsub(/\_id$/, "")
350 label ||= field.gsub(/\_id$/, "")
348 end
351 end
349
352
350 def available_columns
353 def available_columns
351 return @available_columns if @available_columns
354 return @available_columns if @available_columns
352 @available_columns = Query.available_columns
355 @available_columns = Query.available_columns
353 @available_columns += (project ?
356 @available_columns += (project ?
354 project.all_issue_custom_fields :
357 project.all_issue_custom_fields :
355 IssueCustomField.find(:all)
358 IssueCustomField.find(:all)
356 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
359 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
357 end
360 end
358
361
359 def self.available_columns=(v)
362 def self.available_columns=(v)
360 self.available_columns = (v)
363 self.available_columns = (v)
361 end
364 end
362
365
363 def self.add_available_column(column)
366 def self.add_available_column(column)
364 self.available_columns << (column) if column.is_a?(QueryColumn)
367 self.available_columns << (column) if column.is_a?(QueryColumn)
365 end
368 end
366
369
367 # Returns an array of columns that can be used to group the results
370 # Returns an array of columns that can be used to group the results
368 def groupable_columns
371 def groupable_columns
369 available_columns.select {|c| c.groupable}
372 available_columns.select {|c| c.groupable}
370 end
373 end
371
374
372 # Returns a Hash of columns and the key for sorting
375 # Returns a Hash of columns and the key for sorting
373 def sortable_columns
376 def sortable_columns
374 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
377 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
375 h[column.name.to_s] = column.sortable
378 h[column.name.to_s] = column.sortable
376 h
379 h
377 })
380 })
378 end
381 end
379
382
380 def columns
383 def columns
381 # preserve the column_names order
384 # preserve the column_names order
382 (has_default_columns? ? default_columns_names : column_names).collect do |name|
385 (has_default_columns? ? default_columns_names : column_names).collect do |name|
383 available_columns.find { |col| col.name == name }
386 available_columns.find { |col| col.name == name }
384 end.compact
387 end.compact
385 end
388 end
386
389
387 def default_columns_names
390 def default_columns_names
388 @default_columns_names ||= begin
391 @default_columns_names ||= begin
389 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
392 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
390
393
391 project.present? ? default_columns : [:project] | default_columns
394 project.present? ? default_columns : [:project] | default_columns
392 end
395 end
393 end
396 end
394
397
395 def column_names=(names)
398 def column_names=(names)
396 if names
399 if names
397 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
400 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
398 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
401 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
399 # Set column_names to nil if default columns
402 # Set column_names to nil if default columns
400 if names == default_columns_names
403 if names == default_columns_names
401 names = nil
404 names = nil
402 end
405 end
403 end
406 end
404 write_attribute(:column_names, names)
407 write_attribute(:column_names, names)
405 end
408 end
406
409
407 def has_column?(column)
410 def has_column?(column)
408 column_names && column_names.include?(column.name)
411 column_names && column_names.include?(column.name)
409 end
412 end
410
413
411 def has_default_columns?
414 def has_default_columns?
412 column_names.nil? || column_names.empty?
415 column_names.nil? || column_names.empty?
413 end
416 end
414
417
415 def sort_criteria=(arg)
418 def sort_criteria=(arg)
416 c = []
419 c = []
417 if arg.is_a?(Hash)
420 if arg.is_a?(Hash)
418 arg = arg.keys.sort.collect {|k| arg[k]}
421 arg = arg.keys.sort.collect {|k| arg[k]}
419 end
422 end
420 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
423 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
421 write_attribute(:sort_criteria, c)
424 write_attribute(:sort_criteria, c)
422 end
425 end
423
426
424 def sort_criteria
427 def sort_criteria
425 read_attribute(:sort_criteria) || []
428 read_attribute(:sort_criteria) || []
426 end
429 end
427
430
428 def sort_criteria_key(arg)
431 def sort_criteria_key(arg)
429 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
432 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
430 end
433 end
431
434
432 def sort_criteria_order(arg)
435 def sort_criteria_order(arg)
433 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
436 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
434 end
437 end
435
438
436 # Returns the SQL sort order that should be prepended for grouping
439 # Returns the SQL sort order that should be prepended for grouping
437 def group_by_sort_order
440 def group_by_sort_order
438 if grouped? && (column = group_by_column)
441 if grouped? && (column = group_by_column)
439 column.sortable.is_a?(Array) ?
442 column.sortable.is_a?(Array) ?
440 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
443 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
441 "#{column.sortable} #{column.default_order}"
444 "#{column.sortable} #{column.default_order}"
442 end
445 end
443 end
446 end
444
447
445 # Returns true if the query is a grouped query
448 # Returns true if the query is a grouped query
446 def grouped?
449 def grouped?
447 !group_by_column.nil?
450 !group_by_column.nil?
448 end
451 end
449
452
450 def group_by_column
453 def group_by_column
451 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
454 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
452 end
455 end
453
456
454 def group_by_statement
457 def group_by_statement
455 group_by_column.try(:groupable)
458 group_by_column.try(:groupable)
456 end
459 end
457
460
458 def project_statement
461 def project_statement
459 project_clauses = []
462 project_clauses = []
460 if project && !@project.descendants.active.empty?
463 if project && !@project.descendants.active.empty?
461 ids = [project.id]
464 ids = [project.id]
462 if has_filter?("subproject_id")
465 if has_filter?("subproject_id")
463 case operator_for("subproject_id")
466 case operator_for("subproject_id")
464 when '='
467 when '='
465 # include the selected subprojects
468 # include the selected subprojects
466 ids += values_for("subproject_id").each(&:to_i)
469 ids += values_for("subproject_id").each(&:to_i)
467 when '!*'
470 when '!*'
468 # main project only
471 # main project only
469 else
472 else
470 # all subprojects
473 # all subprojects
471 ids += project.descendants.collect(&:id)
474 ids += project.descendants.collect(&:id)
472 end
475 end
473 elsif Setting.display_subprojects_issues?
476 elsif Setting.display_subprojects_issues?
474 ids += project.descendants.collect(&:id)
477 ids += project.descendants.collect(&:id)
475 end
478 end
476 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
479 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
477 elsif project
480 elsif project
478 project_clauses << "#{Project.table_name}.id = %d" % project.id
481 project_clauses << "#{Project.table_name}.id = %d" % project.id
479 end
482 end
480 project_clauses.any? ? project_clauses.join(' AND ') : nil
483 project_clauses.any? ? project_clauses.join(' AND ') : nil
481 end
484 end
482
485
483 def statement
486 def statement
484 # filters clauses
487 # filters clauses
485 filters_clauses = []
488 filters_clauses = []
486 filters.each_key do |field|
489 filters.each_key do |field|
487 next if field == "subproject_id"
490 next if field == "subproject_id"
488 v = values_for(field).clone
491 v = values_for(field).clone
489 next unless v and !v.empty?
492 next unless v and !v.empty?
490 operator = operator_for(field)
493 operator = operator_for(field)
491
494
492 # "me" value subsitution
495 # "me" value subsitution
493 if %w(assigned_to_id author_id watcher_id).include?(field)
496 if %w(assigned_to_id author_id watcher_id).include?(field)
494 if v.delete("me")
497 if v.delete("me")
495 if User.current.logged?
498 if User.current.logged?
496 v.push(User.current.id.to_s)
499 v.push(User.current.id.to_s)
497 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
500 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
498 else
501 else
499 v.push("0")
502 v.push("0")
500 end
503 end
501 end
504 end
502 end
505 end
503
506
504 if field =~ /^cf_(\d+)$/
507 if field =~ /^cf_(\d+)$/
505 # custom field
508 # custom field
506 filters_clauses << sql_for_custom_field(field, operator, v, $1)
509 filters_clauses << sql_for_custom_field(field, operator, v, $1)
507 elsif respond_to?("sql_for_#{field}_field")
510 elsif respond_to?("sql_for_#{field}_field")
508 # specific statement
511 # specific statement
509 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
512 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
510 else
513 else
511 # regular field
514 # regular field
512 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
515 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
513 end
516 end
514 end if filters and valid?
517 end if filters and valid?
515
518
516 filters_clauses << project_statement
519 filters_clauses << project_statement
517 filters_clauses.reject!(&:blank?)
520 filters_clauses.reject!(&:blank?)
518
521
519 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
522 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
520 end
523 end
521
524
522 # Returns the issue count
525 # Returns the issue count
523 def issue_count
526 def issue_count
524 Issue.visible.count(:include => [:status, :project], :conditions => statement)
527 Issue.visible.count(:include => [:status, :project], :conditions => statement)
525 rescue ::ActiveRecord::StatementInvalid => e
528 rescue ::ActiveRecord::StatementInvalid => e
526 raise StatementInvalid.new(e.message)
529 raise StatementInvalid.new(e.message)
527 end
530 end
528
531
529 # Returns the issue count by group or nil if query is not grouped
532 # Returns the issue count by group or nil if query is not grouped
530 def issue_count_by_group
533 def issue_count_by_group
531 r = nil
534 r = nil
532 if grouped?
535 if grouped?
533 begin
536 begin
534 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
537 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
535 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
538 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
536 rescue ActiveRecord::RecordNotFound
539 rescue ActiveRecord::RecordNotFound
537 r = {nil => issue_count}
540 r = {nil => issue_count}
538 end
541 end
539 c = group_by_column
542 c = group_by_column
540 if c.is_a?(QueryCustomFieldColumn)
543 if c.is_a?(QueryCustomFieldColumn)
541 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
544 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
542 end
545 end
543 end
546 end
544 r
547 r
545 rescue ::ActiveRecord::StatementInvalid => e
548 rescue ::ActiveRecord::StatementInvalid => e
546 raise StatementInvalid.new(e.message)
549 raise StatementInvalid.new(e.message)
547 end
550 end
548
551
549 # Returns the issues
552 # Returns the issues
550 # Valid options are :order, :offset, :limit, :include, :conditions
553 # Valid options are :order, :offset, :limit, :include, :conditions
551 def issues(options={})
554 def issues(options={})
552 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
555 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
553 order_option = nil if order_option.blank?
556 order_option = nil if order_option.blank?
554
557
555 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
558 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
556 :conditions => Query.merge_conditions(statement, options[:conditions]),
559 :conditions => Query.merge_conditions(statement, options[:conditions]),
557 :order => order_option,
560 :order => order_option,
558 :limit => options[:limit],
561 :limit => options[:limit],
559 :offset => options[:offset]
562 :offset => options[:offset]
560 rescue ::ActiveRecord::StatementInvalid => e
563 rescue ::ActiveRecord::StatementInvalid => e
561 raise StatementInvalid.new(e.message)
564 raise StatementInvalid.new(e.message)
562 end
565 end
563
566
564 # Returns the journals
567 # Returns the journals
565 # Valid options are :order, :offset, :limit
568 # Valid options are :order, :offset, :limit
566 def journals(options={})
569 def journals(options={})
567 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
570 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
568 :conditions => statement,
571 :conditions => statement,
569 :order => options[:order],
572 :order => options[:order],
570 :limit => options[:limit],
573 :limit => options[:limit],
571 :offset => options[:offset]
574 :offset => options[:offset]
572 rescue ::ActiveRecord::StatementInvalid => e
575 rescue ::ActiveRecord::StatementInvalid => e
573 raise StatementInvalid.new(e.message)
576 raise StatementInvalid.new(e.message)
574 end
577 end
575
578
576 # Returns the versions
579 # Returns the versions
577 # Valid options are :conditions
580 # Valid options are :conditions
578 def versions(options={})
581 def versions(options={})
579 Version.visible.find :all, :include => :project,
582 Version.visible.find :all, :include => :project,
580 :conditions => Query.merge_conditions(project_statement, options[:conditions])
583 :conditions => Query.merge_conditions(project_statement, options[:conditions])
581 rescue ::ActiveRecord::StatementInvalid => e
584 rescue ::ActiveRecord::StatementInvalid => e
582 raise StatementInvalid.new(e.message)
585 raise StatementInvalid.new(e.message)
583 end
586 end
584
587
585 def sql_for_watcher_id_field(field, operator, value)
588 def sql_for_watcher_id_field(field, operator, value)
586 db_table = Watcher.table_name
589 db_table = Watcher.table_name
587 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
590 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
588 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
591 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
589 end
592 end
590
593
591 def sql_for_member_of_group_field(field, operator, value)
594 def sql_for_member_of_group_field(field, operator, value)
592 if operator == '*' # Any group
595 if operator == '*' # Any group
593 groups = Group.all
596 groups = Group.all
594 operator = '=' # Override the operator since we want to find by assigned_to
597 operator = '=' # Override the operator since we want to find by assigned_to
595 elsif operator == "!*"
598 elsif operator == "!*"
596 groups = Group.all
599 groups = Group.all
597 operator = '!' # Override the operator since we want to find by assigned_to
600 operator = '!' # Override the operator since we want to find by assigned_to
598 else
601 else
599 groups = Group.find_all_by_id(value)
602 groups = Group.find_all_by_id(value)
600 end
603 end
601 groups ||= []
604 groups ||= []
602
605
603 members_of_groups = groups.inject([]) {|user_ids, group|
606 members_of_groups = groups.inject([]) {|user_ids, group|
604 if group && group.user_ids.present?
607 if group && group.user_ids.present?
605 user_ids << group.user_ids
608 user_ids << group.user_ids
606 end
609 end
607 user_ids.flatten.uniq.compact
610 user_ids.flatten.uniq.compact
608 }.sort.collect(&:to_s)
611 }.sort.collect(&:to_s)
609
612
610 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
613 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
611 end
614 end
612
615
613 def sql_for_assigned_to_role_field(field, operator, value)
616 def sql_for_assigned_to_role_field(field, operator, value)
614 if operator == "*" # Any Role
617 if operator == "*" # Any Role
615 roles = Role.givable
618 roles = Role.givable
616 operator = '=' # Override the operator since we want to find by assigned_to
619 operator = '=' # Override the operator since we want to find by assigned_to
617 elsif operator == "!*" # No role
620 elsif operator == "!*" # No role
618 roles = Role.givable
621 roles = Role.givable
619 operator = '!' # Override the operator since we want to find by assigned_to
622 operator = '!' # Override the operator since we want to find by assigned_to
620 else
623 else
621 roles = Role.givable.find_all_by_id(value)
624 roles = Role.givable.find_all_by_id(value)
622 end
625 end
623 roles ||= []
626 roles ||= []
624
627
625 members_of_roles = roles.inject([]) {|user_ids, role|
628 members_of_roles = roles.inject([]) {|user_ids, role|
626 if role && role.members
629 if role && role.members
627 user_ids << role.members.collect(&:user_id)
630 user_ids << role.members.collect(&:user_id)
628 end
631 end
629 user_ids.flatten.uniq.compact
632 user_ids.flatten.uniq.compact
630 }.sort.collect(&:to_s)
633 }.sort.collect(&:to_s)
631
634
632 '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
635 '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
633 end
636 end
634
637
635 private
638 private
636
639
637 def sql_for_custom_field(field, operator, value, custom_field_id)
640 def sql_for_custom_field(field, operator, value, custom_field_id)
638 db_table = CustomValue.table_name
641 db_table = CustomValue.table_name
639 db_field = 'value'
642 db_field = 'value'
640 "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
643 "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
641 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
644 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
642 end
645 end
643
646
644 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
647 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
645 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
648 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
646 sql = ''
649 sql = ''
647 case operator
650 case operator
648 when "="
651 when "="
649 if value.any?
652 if value.any?
650 case type_for(field)
653 case type_for(field)
651 when :date, :date_past
654 when :date, :date_past
652 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
655 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
653 when :integer
656 when :integer
654 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
657 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
655 when :float
658 when :float
656 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
659 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
657 else
660 else
658 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
661 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
659 end
662 end
660 else
663 else
661 # IN an empty set
664 # IN an empty set
662 sql = "1=0"
665 sql = "1=0"
663 end
666 end
664 when "!"
667 when "!"
665 if value.any?
668 if value.any?
666 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
669 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
667 else
670 else
668 # NOT IN an empty set
671 # NOT IN an empty set
669 sql = "1=1"
672 sql = "1=1"
670 end
673 end
671 when "!*"
674 when "!*"
672 sql = "#{db_table}.#{db_field} IS NULL"
675 sql = "#{db_table}.#{db_field} IS NULL"
673 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
676 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
674 when "*"
677 when "*"
675 sql = "#{db_table}.#{db_field} IS NOT NULL"
678 sql = "#{db_table}.#{db_field} IS NOT NULL"
676 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
679 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
677 when ">="
680 when ">="
678 if [:date, :date_past].include?(type_for(field))
681 if [:date, :date_past].include?(type_for(field))
679 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
682 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
680 else
683 else
681 if is_custom_filter
684 if is_custom_filter
682 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f}"
685 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f}"
683 else
686 else
684 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
687 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
685 end
688 end
686 end
689 end
687 when "<="
690 when "<="
688 if [:date, :date_past].include?(type_for(field))
691 if [:date, :date_past].include?(type_for(field))
689 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
692 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
690 else
693 else
691 if is_custom_filter
694 if is_custom_filter
692 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f}"
695 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f}"
693 else
696 else
694 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
697 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
695 end
698 end
696 end
699 end
697 when "><"
700 when "><"
698 if [:date, :date_past].include?(type_for(field))
701 if [:date, :date_past].include?(type_for(field))
699 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
702 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
700 else
703 else
701 if is_custom_filter
704 if is_custom_filter
702 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
705 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
703 else
706 else
704 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
707 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
705 end
708 end
706 end
709 end
707 when "o"
710 when "o"
708 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
711 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
709 when "c"
712 when "c"
710 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
713 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
711 when ">t-"
714 when ">t-"
712 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
715 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
713 when "<t-"
716 when "<t-"
714 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
717 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
715 when "t-"
718 when "t-"
716 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
719 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
717 when ">t+"
720 when ">t+"
718 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
721 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
719 when "<t+"
722 when "<t+"
720 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
723 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
721 when "t+"
724 when "t+"
722 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
725 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
723 when "t"
726 when "t"
724 sql = relative_date_clause(db_table, db_field, 0, 0)
727 sql = relative_date_clause(db_table, db_field, 0, 0)
725 when "w"
728 when "w"
726 first_day_of_week = l(:general_first_day_of_week).to_i
729 first_day_of_week = l(:general_first_day_of_week).to_i
727 day_of_week = Date.today.cwday
730 day_of_week = Date.today.cwday
728 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
731 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
729 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
732 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
730 when "~"
733 when "~"
731 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
734 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
732 when "!~"
735 when "!~"
733 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
736 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
734 else
737 else
735 raise "Unknown query operator #{operator}"
738 raise "Unknown query operator #{operator}"
736 end
739 end
737
740
738 return sql
741 return sql
739 end
742 end
740
743
741 def add_custom_fields_filters(custom_fields)
744 def add_custom_fields_filters(custom_fields)
742 @available_filters ||= {}
745 @available_filters ||= {}
743
746
744 custom_fields.select(&:is_filter?).each do |field|
747 custom_fields.select(&:is_filter?).each do |field|
745 case field.field_format
748 case field.field_format
746 when "text"
749 when "text"
747 options = { :type => :text, :order => 20 }
750 options = { :type => :text, :order => 20 }
748 when "list"
751 when "list"
749 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
752 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
750 when "date"
753 when "date"
751 options = { :type => :date, :order => 20 }
754 options = { :type => :date, :order => 20 }
752 when "bool"
755 when "bool"
753 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
756 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
754 when "int"
757 when "int"
755 options = { :type => :integer, :order => 20 }
758 options = { :type => :integer, :order => 20 }
756 when "float"
759 when "float"
757 options = { :type => :float, :order => 20 }
760 options = { :type => :float, :order => 20 }
758 when "user", "version"
761 when "user", "version"
759 next unless project
762 next unless project
760 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
763 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
761 else
764 else
762 options = { :type => :string, :order => 20 }
765 options = { :type => :string, :order => 20 }
763 end
766 end
764 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
767 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
765 end
768 end
766 end
769 end
767
770
768 # Returns a SQL clause for a date or datetime field.
771 # Returns a SQL clause for a date or datetime field.
769 def date_clause(table, field, from, to)
772 def date_clause(table, field, from, to)
770 s = []
773 s = []
771 if from
774 if from
772 from_yesterday = from - 1
775 from_yesterday = from - 1
773 from_yesterday_utc = Time.gm(from_yesterday.year, from_yesterday.month, from_yesterday.day)
776 from_yesterday_utc = Time.gm(from_yesterday.year, from_yesterday.month, from_yesterday.day)
774 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_utc.end_of_day)])
777 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_utc.end_of_day)])
775 end
778 end
776 if to
779 if to
777 to_utc = Time.gm(to.year, to.month, to.day)
780 to_utc = Time.gm(to.year, to.month, to.day)
778 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_utc.end_of_day)])
781 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_utc.end_of_day)])
779 end
782 end
780 s.join(' AND ')
783 s.join(' AND ')
781 end
784 end
782
785
783 # Returns a SQL clause for a date or datetime field using relative dates.
786 # Returns a SQL clause for a date or datetime field using relative dates.
784 def relative_date_clause(table, field, days_from, days_to)
787 def relative_date_clause(table, field, days_from, days_to)
785 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
788 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
786 end
789 end
787 end
790 end
@@ -1,1635 +1,1709
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19 require 'issues_controller'
19 require 'issues_controller'
20
20
21 class IssuesControllerTest < ActionController::TestCase
21 class IssuesControllerTest < ActionController::TestCase
22 fixtures :projects,
22 fixtures :projects,
23 :users,
23 :users,
24 :roles,
24 :roles,
25 :members,
25 :members,
26 :member_roles,
26 :member_roles,
27 :issues,
27 :issues,
28 :issue_statuses,
28 :issue_statuses,
29 :versions,
29 :versions,
30 :trackers,
30 :trackers,
31 :projects_trackers,
31 :projects_trackers,
32 :issue_categories,
32 :issue_categories,
33 :enabled_modules,
33 :enabled_modules,
34 :enumerations,
34 :enumerations,
35 :attachments,
35 :attachments,
36 :workflows,
36 :workflows,
37 :custom_fields,
37 :custom_fields,
38 :custom_values,
38 :custom_values,
39 :custom_fields_projects,
39 :custom_fields_projects,
40 :custom_fields_trackers,
40 :custom_fields_trackers,
41 :time_entries,
41 :time_entries,
42 :journals,
42 :journals,
43 :journal_details,
43 :journal_details,
44 :queries
44 :queries
45
45
46 def setup
46 def setup
47 @controller = IssuesController.new
47 @controller = IssuesController.new
48 @request = ActionController::TestRequest.new
48 @request = ActionController::TestRequest.new
49 @response = ActionController::TestResponse.new
49 @response = ActionController::TestResponse.new
50 User.current = nil
50 User.current = nil
51 end
51 end
52
52
53 def test_index
53 def test_index
54 Setting.default_language = 'en'
54 Setting.default_language = 'en'
55
55
56 get :index
56 get :index
57 assert_response :success
57 assert_response :success
58 assert_template 'index'
58 assert_template 'index'
59 assert_not_nil assigns(:issues)
59 assert_not_nil assigns(:issues)
60 assert_nil assigns(:project)
60 assert_nil assigns(:project)
61 assert_tag :tag => 'a', :content => /Can't print recipes/
61 assert_tag :tag => 'a', :content => /Can't print recipes/
62 assert_tag :tag => 'a', :content => /Subproject issue/
62 assert_tag :tag => 'a', :content => /Subproject issue/
63 # private projects hidden
63 # private projects hidden
64 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
64 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
65 assert_no_tag :tag => 'a', :content => /Issue on project 2/
65 assert_no_tag :tag => 'a', :content => /Issue on project 2/
66 # project column
66 # project column
67 assert_tag :tag => 'th', :content => /Project/
67 assert_tag :tag => 'th', :content => /Project/
68 end
68 end
69
69
70 def test_index_should_not_list_issues_when_module_disabled
70 def test_index_should_not_list_issues_when_module_disabled
71 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
71 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
72 get :index
72 get :index
73 assert_response :success
73 assert_response :success
74 assert_template 'index'
74 assert_template 'index'
75 assert_not_nil assigns(:issues)
75 assert_not_nil assigns(:issues)
76 assert_nil assigns(:project)
76 assert_nil assigns(:project)
77 assert_no_tag :tag => 'a', :content => /Can't print recipes/
77 assert_no_tag :tag => 'a', :content => /Can't print recipes/
78 assert_tag :tag => 'a', :content => /Subproject issue/
78 assert_tag :tag => 'a', :content => /Subproject issue/
79 end
79 end
80
80
81 def test_index_should_list_visible_issues_only
81 def test_index_should_list_visible_issues_only
82 get :index, :per_page => 100
82 get :index, :per_page => 100
83 assert_response :success
83 assert_response :success
84 assert_not_nil assigns(:issues)
84 assert_not_nil assigns(:issues)
85 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
85 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
86 end
86 end
87
87
88 def test_index_with_project
88 def test_index_with_project
89 Setting.display_subprojects_issues = 0
89 Setting.display_subprojects_issues = 0
90 get :index, :project_id => 1
90 get :index, :project_id => 1
91 assert_response :success
91 assert_response :success
92 assert_template 'index'
92 assert_template 'index'
93 assert_not_nil assigns(:issues)
93 assert_not_nil assigns(:issues)
94 assert_tag :tag => 'a', :content => /Can't print recipes/
94 assert_tag :tag => 'a', :content => /Can't print recipes/
95 assert_no_tag :tag => 'a', :content => /Subproject issue/
95 assert_no_tag :tag => 'a', :content => /Subproject issue/
96 end
96 end
97
97
98 def test_index_with_project_and_subprojects
98 def test_index_with_project_and_subprojects
99 Setting.display_subprojects_issues = 1
99 Setting.display_subprojects_issues = 1
100 get :index, :project_id => 1
100 get :index, :project_id => 1
101 assert_response :success
101 assert_response :success
102 assert_template 'index'
102 assert_template 'index'
103 assert_not_nil assigns(:issues)
103 assert_not_nil assigns(:issues)
104 assert_tag :tag => 'a', :content => /Can't print recipes/
104 assert_tag :tag => 'a', :content => /Can't print recipes/
105 assert_tag :tag => 'a', :content => /Subproject issue/
105 assert_tag :tag => 'a', :content => /Subproject issue/
106 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
106 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
107 end
107 end
108
108
109 def test_index_with_project_and_subprojects_should_show_private_subprojects
109 def test_index_with_project_and_subprojects_should_show_private_subprojects
110 @request.session[:user_id] = 2
110 @request.session[:user_id] = 2
111 Setting.display_subprojects_issues = 1
111 Setting.display_subprojects_issues = 1
112 get :index, :project_id => 1
112 get :index, :project_id => 1
113 assert_response :success
113 assert_response :success
114 assert_template 'index'
114 assert_template 'index'
115 assert_not_nil assigns(:issues)
115 assert_not_nil assigns(:issues)
116 assert_tag :tag => 'a', :content => /Can't print recipes/
116 assert_tag :tag => 'a', :content => /Can't print recipes/
117 assert_tag :tag => 'a', :content => /Subproject issue/
117 assert_tag :tag => 'a', :content => /Subproject issue/
118 assert_tag :tag => 'a', :content => /Issue of a private subproject/
118 assert_tag :tag => 'a', :content => /Issue of a private subproject/
119 end
119 end
120
120
121 def test_index_with_project_and_default_filter
121 def test_index_with_project_and_default_filter
122 get :index, :project_id => 1, :set_filter => 1
122 get :index, :project_id => 1, :set_filter => 1
123 assert_response :success
123 assert_response :success
124 assert_template 'index'
124 assert_template 'index'
125 assert_not_nil assigns(:issues)
125 assert_not_nil assigns(:issues)
126
126
127 query = assigns(:query)
127 query = assigns(:query)
128 assert_not_nil query
128 assert_not_nil query
129 # default filter
129 # default filter
130 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
130 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
131 end
131 end
132
132
133 def test_index_with_project_and_filter
133 def test_index_with_project_and_filter
134 get :index, :project_id => 1, :set_filter => 1,
134 get :index, :project_id => 1, :set_filter => 1,
135 :f => ['tracker_id'],
135 :f => ['tracker_id'],
136 :op => {'tracker_id' => '='},
136 :op => {'tracker_id' => '='},
137 :v => {'tracker_id' => ['1']}
137 :v => {'tracker_id' => ['1']}
138 assert_response :success
138 assert_response :success
139 assert_template 'index'
139 assert_template 'index'
140 assert_not_nil assigns(:issues)
140 assert_not_nil assigns(:issues)
141
141
142 query = assigns(:query)
142 query = assigns(:query)
143 assert_not_nil query
143 assert_not_nil query
144 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
144 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
145 end
145 end
146
146
147 def test_index_with_short_filters
148
149 to_test = {
150 'status_id' => {
151 'o' => { :op => 'o', :values => [''] },
152 'c' => { :op => 'c', :values => [''] },
153 '7' => { :op => '=', :values => ['7'] },
154 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
155 '=7' => { :op => '=', :values => ['7'] },
156 '!3' => { :op => '!', :values => ['3'] },
157 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
158 'subject' => {
159 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
160 'o' => { :op => '=', :values => ['o'] },
161 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
162 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
163 'tracker_id' => {
164 '3' => { :op => '=', :values => ['3'] },
165 '=3' => { :op => '=', :values => ['3'] },
166 '*' => { :op => '=', :values => ['*'] },
167 '!*' => { :op => '!', :values => ['*'] }},
168 'start_date' => {
169 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
172 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
173 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
174 '<t+2' => { :op => '<t+', :values => ['2'] },
175 '>t+2' => { :op => '>t+', :values => ['2'] },
176 't+2' => { :op => 't+', :values => ['2'] },
177 't' => { :op => 't', :values => [''] },
178 'w' => { :op => 'w', :values => [''] },
179 '>t-2' => { :op => '>t-', :values => ['2'] },
180 '<t-2' => { :op => '<t-', :values => ['2'] },
181 't-2' => { :op => 't-', :values => ['2'] }},
182 'created_on' => {
183 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
184 '<t+2' => { :op => '=', :values => ['<t+2'] },
185 '>t+2' => { :op => '=', :values => ['>t+2'] },
186 't+2' => { :op => 't', :values => ['+2'] }},
187 'cf_1' => {
188 'c' => { :op => '=', :values => ['c'] },
189 '!c' => { :op => '!', :values => ['c'] },
190 '!*' => { :op => '!*', :values => [''] },
191 '*' => { :op => '*', :values => [''] }},
192 'estimated_hours' => {
193 '=13.4' => { :op => '=', :values => ['13.4'] },
194 '>=45' => { :op => '>=', :values => ['45'] },
195 '<=125' => { :op => '<=', :values => ['125'] },
196 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
197 '!*' => { :op => '!*', :values => [''] },
198 '*' => { :op => '*', :values => [''] }}
199 }
200
201 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
202
203 to_test.each do |field, expression_and_expected|
204 expression_and_expected.each do |filter_expression, expected|
205
206 get :index, :set_filter => 1, field => filter_expression
207
208 assert_response :success
209 assert_template 'index'
210 assert_not_nil assigns(:issues)
211
212 query = assigns(:query)
213 assert_not_nil query
214 assert query.has_filter?(field)
215 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
216 end
217 end
218
219 end
220
147 def test_index_with_project_and_empty_filters
221 def test_index_with_project_and_empty_filters
148 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
149 assert_response :success
223 assert_response :success
150 assert_template 'index'
224 assert_template 'index'
151 assert_not_nil assigns(:issues)
225 assert_not_nil assigns(:issues)
152
226
153 query = assigns(:query)
227 query = assigns(:query)
154 assert_not_nil query
228 assert_not_nil query
155 # no filter
229 # no filter
156 assert_equal({}, query.filters)
230 assert_equal({}, query.filters)
157 end
231 end
158
232
159 def test_index_with_query
233 def test_index_with_query
160 get :index, :project_id => 1, :query_id => 5
234 get :index, :project_id => 1, :query_id => 5
161 assert_response :success
235 assert_response :success
162 assert_template 'index'
236 assert_template 'index'
163 assert_not_nil assigns(:issues)
237 assert_not_nil assigns(:issues)
164 assert_nil assigns(:issue_count_by_group)
238 assert_nil assigns(:issue_count_by_group)
165 end
239 end
166
240
167 def test_index_with_query_grouped_by_tracker
241 def test_index_with_query_grouped_by_tracker
168 get :index, :project_id => 1, :query_id => 6
242 get :index, :project_id => 1, :query_id => 6
169 assert_response :success
243 assert_response :success
170 assert_template 'index'
244 assert_template 'index'
171 assert_not_nil assigns(:issues)
245 assert_not_nil assigns(:issues)
172 assert_not_nil assigns(:issue_count_by_group)
246 assert_not_nil assigns(:issue_count_by_group)
173 end
247 end
174
248
175 def test_index_with_query_grouped_by_list_custom_field
249 def test_index_with_query_grouped_by_list_custom_field
176 get :index, :project_id => 1, :query_id => 9
250 get :index, :project_id => 1, :query_id => 9
177 assert_response :success
251 assert_response :success
178 assert_template 'index'
252 assert_template 'index'
179 assert_not_nil assigns(:issues)
253 assert_not_nil assigns(:issues)
180 assert_not_nil assigns(:issue_count_by_group)
254 assert_not_nil assigns(:issue_count_by_group)
181 end
255 end
182
256
183 def test_private_query_should_not_be_available_to_other_users
257 def test_private_query_should_not_be_available_to_other_users
184 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
258 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
185 @request.session[:user_id] = 3
259 @request.session[:user_id] = 3
186
260
187 get :index, :query_id => q.id
261 get :index, :query_id => q.id
188 assert_response 403
262 assert_response 403
189 end
263 end
190
264
191 def test_private_query_should_be_available_to_its_user
265 def test_private_query_should_be_available_to_its_user
192 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
266 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
193 @request.session[:user_id] = 2
267 @request.session[:user_id] = 2
194
268
195 get :index, :query_id => q.id
269 get :index, :query_id => q.id
196 assert_response :success
270 assert_response :success
197 end
271 end
198
272
199 def test_public_query_should_be_available_to_other_users
273 def test_public_query_should_be_available_to_other_users
200 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
274 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
201 @request.session[:user_id] = 3
275 @request.session[:user_id] = 3
202
276
203 get :index, :query_id => q.id
277 get :index, :query_id => q.id
204 assert_response :success
278 assert_response :success
205 end
279 end
206
280
207 def test_index_sort_by_field_not_included_in_columns
281 def test_index_sort_by_field_not_included_in_columns
208 Setting.issue_list_default_columns = %w(subject author)
282 Setting.issue_list_default_columns = %w(subject author)
209 get :index, :sort => 'tracker'
283 get :index, :sort => 'tracker'
210 end
284 end
211
285
212 def test_index_csv_with_project
286 def test_index_csv_with_project
213 Setting.default_language = 'en'
287 Setting.default_language = 'en'
214
288
215 get :index, :format => 'csv'
289 get :index, :format => 'csv'
216 assert_response :success
290 assert_response :success
217 assert_not_nil assigns(:issues)
291 assert_not_nil assigns(:issues)
218 assert_equal 'text/csv', @response.content_type
292 assert_equal 'text/csv', @response.content_type
219 assert @response.body.starts_with?("#,")
293 assert @response.body.starts_with?("#,")
220
294
221 get :index, :project_id => 1, :format => 'csv'
295 get :index, :project_id => 1, :format => 'csv'
222 assert_response :success
296 assert_response :success
223 assert_not_nil assigns(:issues)
297 assert_not_nil assigns(:issues)
224 assert_equal 'text/csv', @response.content_type
298 assert_equal 'text/csv', @response.content_type
225 end
299 end
226
300
227 def test_index_pdf
301 def test_index_pdf
228 get :index, :format => 'pdf'
302 get :index, :format => 'pdf'
229 assert_response :success
303 assert_response :success
230 assert_not_nil assigns(:issues)
304 assert_not_nil assigns(:issues)
231 assert_equal 'application/pdf', @response.content_type
305 assert_equal 'application/pdf', @response.content_type
232
306
233 get :index, :project_id => 1, :format => 'pdf'
307 get :index, :project_id => 1, :format => 'pdf'
234 assert_response :success
308 assert_response :success
235 assert_not_nil assigns(:issues)
309 assert_not_nil assigns(:issues)
236 assert_equal 'application/pdf', @response.content_type
310 assert_equal 'application/pdf', @response.content_type
237
311
238 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
312 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
239 assert_response :success
313 assert_response :success
240 assert_not_nil assigns(:issues)
314 assert_not_nil assigns(:issues)
241 assert_equal 'application/pdf', @response.content_type
315 assert_equal 'application/pdf', @response.content_type
242 end
316 end
243
317
244 def test_index_pdf_with_query_grouped_by_list_custom_field
318 def test_index_pdf_with_query_grouped_by_list_custom_field
245 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
319 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
246 assert_response :success
320 assert_response :success
247 assert_not_nil assigns(:issues)
321 assert_not_nil assigns(:issues)
248 assert_not_nil assigns(:issue_count_by_group)
322 assert_not_nil assigns(:issue_count_by_group)
249 assert_equal 'application/pdf', @response.content_type
323 assert_equal 'application/pdf', @response.content_type
250 end
324 end
251
325
252 def test_index_sort
326 def test_index_sort
253 get :index, :sort => 'tracker,id:desc'
327 get :index, :sort => 'tracker,id:desc'
254 assert_response :success
328 assert_response :success
255
329
256 sort_params = @request.session['issues_index_sort']
330 sort_params = @request.session['issues_index_sort']
257 assert sort_params.is_a?(String)
331 assert sort_params.is_a?(String)
258 assert_equal 'tracker,id:desc', sort_params
332 assert_equal 'tracker,id:desc', sort_params
259
333
260 issues = assigns(:issues)
334 issues = assigns(:issues)
261 assert_not_nil issues
335 assert_not_nil issues
262 assert !issues.empty?
336 assert !issues.empty?
263 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
337 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
264 end
338 end
265
339
266 def test_index_with_columns
340 def test_index_with_columns
267 columns = ['tracker', 'subject', 'assigned_to']
341 columns = ['tracker', 'subject', 'assigned_to']
268 get :index, :set_filter => 1, :c => columns
342 get :index, :set_filter => 1, :c => columns
269 assert_response :success
343 assert_response :success
270
344
271 # query should use specified columns
345 # query should use specified columns
272 query = assigns(:query)
346 query = assigns(:query)
273 assert_kind_of Query, query
347 assert_kind_of Query, query
274 assert_equal columns, query.column_names.map(&:to_s)
348 assert_equal columns, query.column_names.map(&:to_s)
275
349
276 # columns should be stored in session
350 # columns should be stored in session
277 assert_kind_of Hash, session[:query]
351 assert_kind_of Hash, session[:query]
278 assert_kind_of Array, session[:query][:column_names]
352 assert_kind_of Array, session[:query][:column_names]
279 assert_equal columns, session[:query][:column_names].map(&:to_s)
353 assert_equal columns, session[:query][:column_names].map(&:to_s)
280
354
281 # ensure only these columns are kept in the selected columns list
355 # ensure only these columns are kept in the selected columns list
282 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
356 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
283 :children => { :count => 3 }
357 :children => { :count => 3 }
284 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
358 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
285 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
359 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
286 end
360 end
287
361
288 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
362 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
289 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
363 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
290 get :index, :set_filter => 1
364 get :index, :set_filter => 1
291
365
292 # query should use specified columns
366 # query should use specified columns
293 query = assigns(:query)
367 query = assigns(:query)
294 assert_kind_of Query, query
368 assert_kind_of Query, query
295 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
369 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
296 end
370 end
297
371
298 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
372 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
299 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
373 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
300 columns = ['tracker', 'subject', 'assigned_to']
374 columns = ['tracker', 'subject', 'assigned_to']
301 get :index, :set_filter => 1, :c => columns
375 get :index, :set_filter => 1, :c => columns
302
376
303 # query should use specified columns
377 # query should use specified columns
304 query = assigns(:query)
378 query = assigns(:query)
305 assert_kind_of Query, query
379 assert_kind_of Query, query
306 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
380 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
307 end
381 end
308
382
309 def test_index_with_custom_field_column
383 def test_index_with_custom_field_column
310 columns = %w(tracker subject cf_2)
384 columns = %w(tracker subject cf_2)
311 get :index, :set_filter => 1, :c => columns
385 get :index, :set_filter => 1, :c => columns
312 assert_response :success
386 assert_response :success
313
387
314 # query should use specified columns
388 # query should use specified columns
315 query = assigns(:query)
389 query = assigns(:query)
316 assert_kind_of Query, query
390 assert_kind_of Query, query
317 assert_equal columns, query.column_names.map(&:to_s)
391 assert_equal columns, query.column_names.map(&:to_s)
318
392
319 assert_tag :td,
393 assert_tag :td,
320 :attributes => {:class => 'cf_2 string'},
394 :attributes => {:class => 'cf_2 string'},
321 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
395 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
322 end
396 end
323
397
324 def test_index_send_html_if_query_is_invalid
398 def test_index_send_html_if_query_is_invalid
325 get :index, :f => ['start_date'], :op => {:start_date => '='}
399 get :index, :f => ['start_date'], :op => {:start_date => '='}
326 assert_equal 'text/html', @response.content_type
400 assert_equal 'text/html', @response.content_type
327 assert_template 'index'
401 assert_template 'index'
328 end
402 end
329
403
330 def test_index_send_nothing_if_query_is_invalid
404 def test_index_send_nothing_if_query_is_invalid
331 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
405 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
332 assert_equal 'text/csv', @response.content_type
406 assert_equal 'text/csv', @response.content_type
333 assert @response.body.blank?
407 assert @response.body.blank?
334 end
408 end
335
409
336 def test_show_by_anonymous
410 def test_show_by_anonymous
337 get :show, :id => 1
411 get :show, :id => 1
338 assert_response :success
412 assert_response :success
339 assert_template 'show'
413 assert_template 'show'
340 assert_not_nil assigns(:issue)
414 assert_not_nil assigns(:issue)
341 assert_equal Issue.find(1), assigns(:issue)
415 assert_equal Issue.find(1), assigns(:issue)
342
416
343 # anonymous role is allowed to add a note
417 # anonymous role is allowed to add a note
344 assert_tag :tag => 'form',
418 assert_tag :tag => 'form',
345 :descendant => { :tag => 'fieldset',
419 :descendant => { :tag => 'fieldset',
346 :child => { :tag => 'legend',
420 :child => { :tag => 'legend',
347 :content => /Notes/ } }
421 :content => /Notes/ } }
348 end
422 end
349
423
350 def test_show_by_manager
424 def test_show_by_manager
351 @request.session[:user_id] = 2
425 @request.session[:user_id] = 2
352 get :show, :id => 1
426 get :show, :id => 1
353 assert_response :success
427 assert_response :success
354
428
355 assert_tag :tag => 'a',
429 assert_tag :tag => 'a',
356 :content => /Quote/
430 :content => /Quote/
357
431
358 assert_tag :tag => 'form',
432 assert_tag :tag => 'form',
359 :descendant => { :tag => 'fieldset',
433 :descendant => { :tag => 'fieldset',
360 :child => { :tag => 'legend',
434 :child => { :tag => 'legend',
361 :content => /Change properties/ } },
435 :content => /Change properties/ } },
362 :descendant => { :tag => 'fieldset',
436 :descendant => { :tag => 'fieldset',
363 :child => { :tag => 'legend',
437 :child => { :tag => 'legend',
364 :content => /Log time/ } },
438 :content => /Log time/ } },
365 :descendant => { :tag => 'fieldset',
439 :descendant => { :tag => 'fieldset',
366 :child => { :tag => 'legend',
440 :child => { :tag => 'legend',
367 :content => /Notes/ } }
441 :content => /Notes/ } }
368 end
442 end
369
443
370 def test_update_form_should_not_display_inactive_enumerations
444 def test_update_form_should_not_display_inactive_enumerations
371 @request.session[:user_id] = 2
445 @request.session[:user_id] = 2
372 get :show, :id => 1
446 get :show, :id => 1
373 assert_response :success
447 assert_response :success
374
448
375 assert ! IssuePriority.find(15).active?
449 assert ! IssuePriority.find(15).active?
376 assert_no_tag :option, :attributes => {:value => '15'},
450 assert_no_tag :option, :attributes => {:value => '15'},
377 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
451 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
378 end
452 end
379
453
380 def test_update_form_should_allow_attachment_upload
454 def test_update_form_should_allow_attachment_upload
381 @request.session[:user_id] = 2
455 @request.session[:user_id] = 2
382 get :show, :id => 1
456 get :show, :id => 1
383
457
384 assert_tag :tag => 'form',
458 assert_tag :tag => 'form',
385 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
459 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
386 :descendant => {
460 :descendant => {
387 :tag => 'input',
461 :tag => 'input',
388 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
462 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
389 }
463 }
390 end
464 end
391
465
392 def test_show_should_deny_anonymous_access_without_permission
466 def test_show_should_deny_anonymous_access_without_permission
393 Role.anonymous.remove_permission!(:view_issues)
467 Role.anonymous.remove_permission!(:view_issues)
394 get :show, :id => 1
468 get :show, :id => 1
395 assert_response :redirect
469 assert_response :redirect
396 end
470 end
397
471
398 def test_show_should_deny_anonymous_access_to_private_issue
472 def test_show_should_deny_anonymous_access_to_private_issue
399 Issue.update_all(["is_private = ?", true], "id = 1")
473 Issue.update_all(["is_private = ?", true], "id = 1")
400 get :show, :id => 1
474 get :show, :id => 1
401 assert_response :redirect
475 assert_response :redirect
402 end
476 end
403
477
404 def test_show_should_deny_non_member_access_without_permission
478 def test_show_should_deny_non_member_access_without_permission
405 Role.non_member.remove_permission!(:view_issues)
479 Role.non_member.remove_permission!(:view_issues)
406 @request.session[:user_id] = 9
480 @request.session[:user_id] = 9
407 get :show, :id => 1
481 get :show, :id => 1
408 assert_response 403
482 assert_response 403
409 end
483 end
410
484
411 def test_show_should_deny_non_member_access_to_private_issue
485 def test_show_should_deny_non_member_access_to_private_issue
412 Issue.update_all(["is_private = ?", true], "id = 1")
486 Issue.update_all(["is_private = ?", true], "id = 1")
413 @request.session[:user_id] = 9
487 @request.session[:user_id] = 9
414 get :show, :id => 1
488 get :show, :id => 1
415 assert_response 403
489 assert_response 403
416 end
490 end
417
491
418 def test_show_should_deny_member_access_without_permission
492 def test_show_should_deny_member_access_without_permission
419 Role.find(1).remove_permission!(:view_issues)
493 Role.find(1).remove_permission!(:view_issues)
420 @request.session[:user_id] = 2
494 @request.session[:user_id] = 2
421 get :show, :id => 1
495 get :show, :id => 1
422 assert_response 403
496 assert_response 403
423 end
497 end
424
498
425 def test_show_should_deny_member_access_to_private_issue_without_permission
499 def test_show_should_deny_member_access_to_private_issue_without_permission
426 Issue.update_all(["is_private = ?", true], "id = 1")
500 Issue.update_all(["is_private = ?", true], "id = 1")
427 @request.session[:user_id] = 3
501 @request.session[:user_id] = 3
428 get :show, :id => 1
502 get :show, :id => 1
429 assert_response 403
503 assert_response 403
430 end
504 end
431
505
432 def test_show_should_allow_author_access_to_private_issue
506 def test_show_should_allow_author_access_to_private_issue
433 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
507 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
434 @request.session[:user_id] = 3
508 @request.session[:user_id] = 3
435 get :show, :id => 1
509 get :show, :id => 1
436 assert_response :success
510 assert_response :success
437 end
511 end
438
512
439 def test_show_should_allow_assignee_access_to_private_issue
513 def test_show_should_allow_assignee_access_to_private_issue
440 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
514 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
441 @request.session[:user_id] = 3
515 @request.session[:user_id] = 3
442 get :show, :id => 1
516 get :show, :id => 1
443 assert_response :success
517 assert_response :success
444 end
518 end
445
519
446 def test_show_should_allow_member_access_to_private_issue_with_permission
520 def test_show_should_allow_member_access_to_private_issue_with_permission
447 Issue.update_all(["is_private = ?", true], "id = 1")
521 Issue.update_all(["is_private = ?", true], "id = 1")
448 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
522 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
449 @request.session[:user_id] = 3
523 @request.session[:user_id] = 3
450 get :show, :id => 1
524 get :show, :id => 1
451 assert_response :success
525 assert_response :success
452 end
526 end
453
527
454 def test_show_should_not_disclose_relations_to_invisible_issues
528 def test_show_should_not_disclose_relations_to_invisible_issues
455 Setting.cross_project_issue_relations = '1'
529 Setting.cross_project_issue_relations = '1'
456 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
530 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
457 # Relation to a private project issue
531 # Relation to a private project issue
458 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
532 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
459
533
460 get :show, :id => 1
534 get :show, :id => 1
461 assert_response :success
535 assert_response :success
462
536
463 assert_tag :div, :attributes => { :id => 'relations' },
537 assert_tag :div, :attributes => { :id => 'relations' },
464 :descendant => { :tag => 'a', :content => /#2$/ }
538 :descendant => { :tag => 'a', :content => /#2$/ }
465 assert_no_tag :div, :attributes => { :id => 'relations' },
539 assert_no_tag :div, :attributes => { :id => 'relations' },
466 :descendant => { :tag => 'a', :content => /#4$/ }
540 :descendant => { :tag => 'a', :content => /#4$/ }
467 end
541 end
468
542
469 def test_show_atom
543 def test_show_atom
470 get :show, :id => 2, :format => 'atom'
544 get :show, :id => 2, :format => 'atom'
471 assert_response :success
545 assert_response :success
472 assert_template 'journals/index'
546 assert_template 'journals/index'
473 # Inline image
547 # Inline image
474 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
548 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
475 end
549 end
476
550
477 def test_show_export_to_pdf
551 def test_show_export_to_pdf
478 get :show, :id => 3, :format => 'pdf'
552 get :show, :id => 3, :format => 'pdf'
479 assert_response :success
553 assert_response :success
480 assert_equal 'application/pdf', @response.content_type
554 assert_equal 'application/pdf', @response.content_type
481 assert @response.body.starts_with?('%PDF')
555 assert @response.body.starts_with?('%PDF')
482 assert_not_nil assigns(:issue)
556 assert_not_nil assigns(:issue)
483 end
557 end
484
558
485 def test_get_new
559 def test_get_new
486 @request.session[:user_id] = 2
560 @request.session[:user_id] = 2
487 get :new, :project_id => 1, :tracker_id => 1
561 get :new, :project_id => 1, :tracker_id => 1
488 assert_response :success
562 assert_response :success
489 assert_template 'new'
563 assert_template 'new'
490
564
491 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
565 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
492 :value => 'Default string' }
566 :value => 'Default string' }
493
567
494 # Be sure we don't display inactive IssuePriorities
568 # Be sure we don't display inactive IssuePriorities
495 assert ! IssuePriority.find(15).active?
569 assert ! IssuePriority.find(15).active?
496 assert_no_tag :option, :attributes => {:value => '15'},
570 assert_no_tag :option, :attributes => {:value => '15'},
497 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
571 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
498 end
572 end
499
573
500 def test_get_new_form_should_allow_attachment_upload
574 def test_get_new_form_should_allow_attachment_upload
501 @request.session[:user_id] = 2
575 @request.session[:user_id] = 2
502 get :new, :project_id => 1, :tracker_id => 1
576 get :new, :project_id => 1, :tracker_id => 1
503
577
504 assert_tag :tag => 'form',
578 assert_tag :tag => 'form',
505 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
579 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
506 :descendant => {
580 :descendant => {
507 :tag => 'input',
581 :tag => 'input',
508 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
582 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
509 }
583 }
510 end
584 end
511
585
512 def test_get_new_without_tracker_id
586 def test_get_new_without_tracker_id
513 @request.session[:user_id] = 2
587 @request.session[:user_id] = 2
514 get :new, :project_id => 1
588 get :new, :project_id => 1
515 assert_response :success
589 assert_response :success
516 assert_template 'new'
590 assert_template 'new'
517
591
518 issue = assigns(:issue)
592 issue = assigns(:issue)
519 assert_not_nil issue
593 assert_not_nil issue
520 assert_equal Project.find(1).trackers.first, issue.tracker
594 assert_equal Project.find(1).trackers.first, issue.tracker
521 end
595 end
522
596
523 def test_get_new_with_no_default_status_should_display_an_error
597 def test_get_new_with_no_default_status_should_display_an_error
524 @request.session[:user_id] = 2
598 @request.session[:user_id] = 2
525 IssueStatus.delete_all
599 IssueStatus.delete_all
526
600
527 get :new, :project_id => 1
601 get :new, :project_id => 1
528 assert_response 500
602 assert_response 500
529 assert_error_tag :content => /No default issue/
603 assert_error_tag :content => /No default issue/
530 end
604 end
531
605
532 def test_get_new_with_no_tracker_should_display_an_error
606 def test_get_new_with_no_tracker_should_display_an_error
533 @request.session[:user_id] = 2
607 @request.session[:user_id] = 2
534 Tracker.delete_all
608 Tracker.delete_all
535
609
536 get :new, :project_id => 1
610 get :new, :project_id => 1
537 assert_response 500
611 assert_response 500
538 assert_error_tag :content => /No tracker/
612 assert_error_tag :content => /No tracker/
539 end
613 end
540
614
541 def test_update_new_form
615 def test_update_new_form
542 @request.session[:user_id] = 2
616 @request.session[:user_id] = 2
543 xhr :post, :new, :project_id => 1,
617 xhr :post, :new, :project_id => 1,
544 :issue => {:tracker_id => 2,
618 :issue => {:tracker_id => 2,
545 :subject => 'This is the test_new issue',
619 :subject => 'This is the test_new issue',
546 :description => 'This is the description',
620 :description => 'This is the description',
547 :priority_id => 5}
621 :priority_id => 5}
548 assert_response :success
622 assert_response :success
549 assert_template 'attributes'
623 assert_template 'attributes'
550
624
551 issue = assigns(:issue)
625 issue = assigns(:issue)
552 assert_kind_of Issue, issue
626 assert_kind_of Issue, issue
553 assert_equal 1, issue.project_id
627 assert_equal 1, issue.project_id
554 assert_equal 2, issue.tracker_id
628 assert_equal 2, issue.tracker_id
555 assert_equal 'This is the test_new issue', issue.subject
629 assert_equal 'This is the test_new issue', issue.subject
556 end
630 end
557
631
558 def test_post_create
632 def test_post_create
559 @request.session[:user_id] = 2
633 @request.session[:user_id] = 2
560 assert_difference 'Issue.count' do
634 assert_difference 'Issue.count' do
561 post :create, :project_id => 1,
635 post :create, :project_id => 1,
562 :issue => {:tracker_id => 3,
636 :issue => {:tracker_id => 3,
563 :status_id => 2,
637 :status_id => 2,
564 :subject => 'This is the test_new issue',
638 :subject => 'This is the test_new issue',
565 :description => 'This is the description',
639 :description => 'This is the description',
566 :priority_id => 5,
640 :priority_id => 5,
567 :start_date => '2010-11-07',
641 :start_date => '2010-11-07',
568 :estimated_hours => '',
642 :estimated_hours => '',
569 :custom_field_values => {'2' => 'Value for field 2'}}
643 :custom_field_values => {'2' => 'Value for field 2'}}
570 end
644 end
571 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
645 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
572
646
573 issue = Issue.find_by_subject('This is the test_new issue')
647 issue = Issue.find_by_subject('This is the test_new issue')
574 assert_not_nil issue
648 assert_not_nil issue
575 assert_equal 2, issue.author_id
649 assert_equal 2, issue.author_id
576 assert_equal 3, issue.tracker_id
650 assert_equal 3, issue.tracker_id
577 assert_equal 2, issue.status_id
651 assert_equal 2, issue.status_id
578 assert_equal Date.parse('2010-11-07'), issue.start_date
652 assert_equal Date.parse('2010-11-07'), issue.start_date
579 assert_nil issue.estimated_hours
653 assert_nil issue.estimated_hours
580 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
654 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
581 assert_not_nil v
655 assert_not_nil v
582 assert_equal 'Value for field 2', v.value
656 assert_equal 'Value for field 2', v.value
583 end
657 end
584
658
585 def test_post_new_with_group_assignment
659 def test_post_new_with_group_assignment
586 group = Group.find(11)
660 group = Group.find(11)
587 project = Project.find(1)
661 project = Project.find(1)
588 project.members << Member.new(:principal => group, :roles => [Role.first])
662 project.members << Member.new(:principal => group, :roles => [Role.first])
589
663
590 with_settings :issue_group_assignment => '1' do
664 with_settings :issue_group_assignment => '1' do
591 @request.session[:user_id] = 2
665 @request.session[:user_id] = 2
592 assert_difference 'Issue.count' do
666 assert_difference 'Issue.count' do
593 post :create, :project_id => project.id,
667 post :create, :project_id => project.id,
594 :issue => {:tracker_id => 3,
668 :issue => {:tracker_id => 3,
595 :status_id => 1,
669 :status_id => 1,
596 :subject => 'This is the test_new_with_group_assignment issue',
670 :subject => 'This is the test_new_with_group_assignment issue',
597 :assigned_to_id => group.id}
671 :assigned_to_id => group.id}
598 end
672 end
599 end
673 end
600 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
674 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
601
675
602 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
676 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
603 assert_not_nil issue
677 assert_not_nil issue
604 assert_equal group, issue.assigned_to
678 assert_equal group, issue.assigned_to
605 end
679 end
606
680
607 def test_post_create_without_start_date
681 def test_post_create_without_start_date
608 @request.session[:user_id] = 2
682 @request.session[:user_id] = 2
609 assert_difference 'Issue.count' do
683 assert_difference 'Issue.count' do
610 post :create, :project_id => 1,
684 post :create, :project_id => 1,
611 :issue => {:tracker_id => 3,
685 :issue => {:tracker_id => 3,
612 :status_id => 2,
686 :status_id => 2,
613 :subject => 'This is the test_new issue',
687 :subject => 'This is the test_new issue',
614 :description => 'This is the description',
688 :description => 'This is the description',
615 :priority_id => 5,
689 :priority_id => 5,
616 :start_date => '',
690 :start_date => '',
617 :estimated_hours => '',
691 :estimated_hours => '',
618 :custom_field_values => {'2' => 'Value for field 2'}}
692 :custom_field_values => {'2' => 'Value for field 2'}}
619 end
693 end
620 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
694 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
621
695
622 issue = Issue.find_by_subject('This is the test_new issue')
696 issue = Issue.find_by_subject('This is the test_new issue')
623 assert_not_nil issue
697 assert_not_nil issue
624 assert_nil issue.start_date
698 assert_nil issue.start_date
625 end
699 end
626
700
627 def test_post_create_and_continue
701 def test_post_create_and_continue
628 @request.session[:user_id] = 2
702 @request.session[:user_id] = 2
629 assert_difference 'Issue.count' do
703 assert_difference 'Issue.count' do
630 post :create, :project_id => 1,
704 post :create, :project_id => 1,
631 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
705 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
632 :continue => ''
706 :continue => ''
633 end
707 end
634
708
635 issue = Issue.first(:order => 'id DESC')
709 issue = Issue.first(:order => 'id DESC')
636 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
710 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
637 assert_not_nil flash[:notice], "flash was not set"
711 assert_not_nil flash[:notice], "flash was not set"
638 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
712 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
639 end
713 end
640
714
641 def test_post_create_without_custom_fields_param
715 def test_post_create_without_custom_fields_param
642 @request.session[:user_id] = 2
716 @request.session[:user_id] = 2
643 assert_difference 'Issue.count' do
717 assert_difference 'Issue.count' do
644 post :create, :project_id => 1,
718 post :create, :project_id => 1,
645 :issue => {:tracker_id => 1,
719 :issue => {:tracker_id => 1,
646 :subject => 'This is the test_new issue',
720 :subject => 'This is the test_new issue',
647 :description => 'This is the description',
721 :description => 'This is the description',
648 :priority_id => 5}
722 :priority_id => 5}
649 end
723 end
650 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
724 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
651 end
725 end
652
726
653 def test_post_create_with_required_custom_field_and_without_custom_fields_param
727 def test_post_create_with_required_custom_field_and_without_custom_fields_param
654 field = IssueCustomField.find_by_name('Database')
728 field = IssueCustomField.find_by_name('Database')
655 field.update_attribute(:is_required, true)
729 field.update_attribute(:is_required, true)
656
730
657 @request.session[:user_id] = 2
731 @request.session[:user_id] = 2
658 post :create, :project_id => 1,
732 post :create, :project_id => 1,
659 :issue => {:tracker_id => 1,
733 :issue => {:tracker_id => 1,
660 :subject => 'This is the test_new issue',
734 :subject => 'This is the test_new issue',
661 :description => 'This is the description',
735 :description => 'This is the description',
662 :priority_id => 5}
736 :priority_id => 5}
663 assert_response :success
737 assert_response :success
664 assert_template 'new'
738 assert_template 'new'
665 issue = assigns(:issue)
739 issue = assigns(:issue)
666 assert_not_nil issue
740 assert_not_nil issue
667 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
741 assert_equal I18n.translate('activerecord.errors.messages.invalid'), issue.errors.on(:custom_values)
668 end
742 end
669
743
670 def test_post_create_with_watchers
744 def test_post_create_with_watchers
671 @request.session[:user_id] = 2
745 @request.session[:user_id] = 2
672 ActionMailer::Base.deliveries.clear
746 ActionMailer::Base.deliveries.clear
673
747
674 assert_difference 'Watcher.count', 2 do
748 assert_difference 'Watcher.count', 2 do
675 post :create, :project_id => 1,
749 post :create, :project_id => 1,
676 :issue => {:tracker_id => 1,
750 :issue => {:tracker_id => 1,
677 :subject => 'This is a new issue with watchers',
751 :subject => 'This is a new issue with watchers',
678 :description => 'This is the description',
752 :description => 'This is the description',
679 :priority_id => 5,
753 :priority_id => 5,
680 :watcher_user_ids => ['2', '3']}
754 :watcher_user_ids => ['2', '3']}
681 end
755 end
682 issue = Issue.find_by_subject('This is a new issue with watchers')
756 issue = Issue.find_by_subject('This is a new issue with watchers')
683 assert_not_nil issue
757 assert_not_nil issue
684 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
758 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
685
759
686 # Watchers added
760 # Watchers added
687 assert_equal [2, 3], issue.watcher_user_ids.sort
761 assert_equal [2, 3], issue.watcher_user_ids.sort
688 assert issue.watched_by?(User.find(3))
762 assert issue.watched_by?(User.find(3))
689 # Watchers notified
763 # Watchers notified
690 mail = ActionMailer::Base.deliveries.last
764 mail = ActionMailer::Base.deliveries.last
691 assert_kind_of TMail::Mail, mail
765 assert_kind_of TMail::Mail, mail
692 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
766 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
693 end
767 end
694
768
695 def test_post_create_subissue
769 def test_post_create_subissue
696 @request.session[:user_id] = 2
770 @request.session[:user_id] = 2
697
771
698 assert_difference 'Issue.count' do
772 assert_difference 'Issue.count' do
699 post :create, :project_id => 1,
773 post :create, :project_id => 1,
700 :issue => {:tracker_id => 1,
774 :issue => {:tracker_id => 1,
701 :subject => 'This is a child issue',
775 :subject => 'This is a child issue',
702 :parent_issue_id => 2}
776 :parent_issue_id => 2}
703 end
777 end
704 issue = Issue.find_by_subject('This is a child issue')
778 issue = Issue.find_by_subject('This is a child issue')
705 assert_not_nil issue
779 assert_not_nil issue
706 assert_equal Issue.find(2), issue.parent
780 assert_equal Issue.find(2), issue.parent
707 end
781 end
708
782
709 def test_post_create_subissue_with_non_numeric_parent_id
783 def test_post_create_subissue_with_non_numeric_parent_id
710 @request.session[:user_id] = 2
784 @request.session[:user_id] = 2
711
785
712 assert_difference 'Issue.count' do
786 assert_difference 'Issue.count' do
713 post :create, :project_id => 1,
787 post :create, :project_id => 1,
714 :issue => {:tracker_id => 1,
788 :issue => {:tracker_id => 1,
715 :subject => 'This is a child issue',
789 :subject => 'This is a child issue',
716 :parent_issue_id => 'ABC'}
790 :parent_issue_id => 'ABC'}
717 end
791 end
718 issue = Issue.find_by_subject('This is a child issue')
792 issue = Issue.find_by_subject('This is a child issue')
719 assert_not_nil issue
793 assert_not_nil issue
720 assert_nil issue.parent
794 assert_nil issue.parent
721 end
795 end
722
796
723 def test_post_create_private
797 def test_post_create_private
724 @request.session[:user_id] = 2
798 @request.session[:user_id] = 2
725
799
726 assert_difference 'Issue.count' do
800 assert_difference 'Issue.count' do
727 post :create, :project_id => 1,
801 post :create, :project_id => 1,
728 :issue => {:tracker_id => 1,
802 :issue => {:tracker_id => 1,
729 :subject => 'This is a private issue',
803 :subject => 'This is a private issue',
730 :is_private => '1'}
804 :is_private => '1'}
731 end
805 end
732 issue = Issue.first(:order => 'id DESC')
806 issue = Issue.first(:order => 'id DESC')
733 assert issue.is_private?
807 assert issue.is_private?
734 end
808 end
735
809
736 def test_post_create_private_with_set_own_issues_private_permission
810 def test_post_create_private_with_set_own_issues_private_permission
737 role = Role.find(1)
811 role = Role.find(1)
738 role.remove_permission! :set_issues_private
812 role.remove_permission! :set_issues_private
739 role.add_permission! :set_own_issues_private
813 role.add_permission! :set_own_issues_private
740
814
741 @request.session[:user_id] = 2
815 @request.session[:user_id] = 2
742
816
743 assert_difference 'Issue.count' do
817 assert_difference 'Issue.count' do
744 post :create, :project_id => 1,
818 post :create, :project_id => 1,
745 :issue => {:tracker_id => 1,
819 :issue => {:tracker_id => 1,
746 :subject => 'This is a private issue',
820 :subject => 'This is a private issue',
747 :is_private => '1'}
821 :is_private => '1'}
748 end
822 end
749 issue = Issue.first(:order => 'id DESC')
823 issue = Issue.first(:order => 'id DESC')
750 assert issue.is_private?
824 assert issue.is_private?
751 end
825 end
752
826
753 def test_post_create_should_send_a_notification
827 def test_post_create_should_send_a_notification
754 ActionMailer::Base.deliveries.clear
828 ActionMailer::Base.deliveries.clear
755 @request.session[:user_id] = 2
829 @request.session[:user_id] = 2
756 assert_difference 'Issue.count' do
830 assert_difference 'Issue.count' do
757 post :create, :project_id => 1,
831 post :create, :project_id => 1,
758 :issue => {:tracker_id => 3,
832 :issue => {:tracker_id => 3,
759 :subject => 'This is the test_new issue',
833 :subject => 'This is the test_new issue',
760 :description => 'This is the description',
834 :description => 'This is the description',
761 :priority_id => 5,
835 :priority_id => 5,
762 :estimated_hours => '',
836 :estimated_hours => '',
763 :custom_field_values => {'2' => 'Value for field 2'}}
837 :custom_field_values => {'2' => 'Value for field 2'}}
764 end
838 end
765 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
839 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
766
840
767 assert_equal 1, ActionMailer::Base.deliveries.size
841 assert_equal 1, ActionMailer::Base.deliveries.size
768 end
842 end
769
843
770 def test_post_create_should_preserve_fields_values_on_validation_failure
844 def test_post_create_should_preserve_fields_values_on_validation_failure
771 @request.session[:user_id] = 2
845 @request.session[:user_id] = 2
772 post :create, :project_id => 1,
846 post :create, :project_id => 1,
773 :issue => {:tracker_id => 1,
847 :issue => {:tracker_id => 1,
774 # empty subject
848 # empty subject
775 :subject => '',
849 :subject => '',
776 :description => 'This is a description',
850 :description => 'This is a description',
777 :priority_id => 6,
851 :priority_id => 6,
778 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
852 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
779 assert_response :success
853 assert_response :success
780 assert_template 'new'
854 assert_template 'new'
781
855
782 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
856 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
783 :content => 'This is a description'
857 :content => 'This is a description'
784 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
858 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
785 :child => { :tag => 'option', :attributes => { :selected => 'selected',
859 :child => { :tag => 'option', :attributes => { :selected => 'selected',
786 :value => '6' },
860 :value => '6' },
787 :content => 'High' }
861 :content => 'High' }
788 # Custom fields
862 # Custom fields
789 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
863 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
790 :child => { :tag => 'option', :attributes => { :selected => 'selected',
864 :child => { :tag => 'option', :attributes => { :selected => 'selected',
791 :value => 'Oracle' },
865 :value => 'Oracle' },
792 :content => 'Oracle' }
866 :content => 'Oracle' }
793 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
867 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
794 :value => 'Value for field 2'}
868 :value => 'Value for field 2'}
795 end
869 end
796
870
797 def test_post_create_should_ignore_non_safe_attributes
871 def test_post_create_should_ignore_non_safe_attributes
798 @request.session[:user_id] = 2
872 @request.session[:user_id] = 2
799 assert_nothing_raised do
873 assert_nothing_raised do
800 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
874 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
801 end
875 end
802 end
876 end
803
877
804 def test_post_create_with_attachment
878 def test_post_create_with_attachment
805 set_tmp_attachments_directory
879 set_tmp_attachments_directory
806 @request.session[:user_id] = 2
880 @request.session[:user_id] = 2
807
881
808 assert_difference 'Issue.count' do
882 assert_difference 'Issue.count' do
809 assert_difference 'Attachment.count' do
883 assert_difference 'Attachment.count' do
810 post :create, :project_id => 1,
884 post :create, :project_id => 1,
811 :issue => { :tracker_id => '1', :subject => 'With attachment' },
885 :issue => { :tracker_id => '1', :subject => 'With attachment' },
812 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
886 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
813 end
887 end
814 end
888 end
815
889
816 issue = Issue.first(:order => 'id DESC')
890 issue = Issue.first(:order => 'id DESC')
817 attachment = Attachment.first(:order => 'id DESC')
891 attachment = Attachment.first(:order => 'id DESC')
818
892
819 assert_equal issue, attachment.container
893 assert_equal issue, attachment.container
820 assert_equal 2, attachment.author_id
894 assert_equal 2, attachment.author_id
821 assert_equal 'testfile.txt', attachment.filename
895 assert_equal 'testfile.txt', attachment.filename
822 assert_equal 'text/plain', attachment.content_type
896 assert_equal 'text/plain', attachment.content_type
823 assert_equal 'test file', attachment.description
897 assert_equal 'test file', attachment.description
824 assert_equal 59, attachment.filesize
898 assert_equal 59, attachment.filesize
825 assert File.exists?(attachment.diskfile)
899 assert File.exists?(attachment.diskfile)
826 assert_equal 59, File.size(attachment.diskfile)
900 assert_equal 59, File.size(attachment.diskfile)
827 end
901 end
828
902
829 context "without workflow privilege" do
903 context "without workflow privilege" do
830 setup do
904 setup do
831 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
905 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
832 Role.anonymous.add_permission! :add_issues, :add_issue_notes
906 Role.anonymous.add_permission! :add_issues, :add_issue_notes
833 end
907 end
834
908
835 context "#new" do
909 context "#new" do
836 should "propose default status only" do
910 should "propose default status only" do
837 get :new, :project_id => 1
911 get :new, :project_id => 1
838 assert_response :success
912 assert_response :success
839 assert_template 'new'
913 assert_template 'new'
840 assert_tag :tag => 'select',
914 assert_tag :tag => 'select',
841 :attributes => {:name => 'issue[status_id]'},
915 :attributes => {:name => 'issue[status_id]'},
842 :children => {:count => 1},
916 :children => {:count => 1},
843 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
917 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
844 end
918 end
845
919
846 should "accept default status" do
920 should "accept default status" do
847 assert_difference 'Issue.count' do
921 assert_difference 'Issue.count' do
848 post :create, :project_id => 1,
922 post :create, :project_id => 1,
849 :issue => {:tracker_id => 1,
923 :issue => {:tracker_id => 1,
850 :subject => 'This is an issue',
924 :subject => 'This is an issue',
851 :status_id => 1}
925 :status_id => 1}
852 end
926 end
853 issue = Issue.last(:order => 'id')
927 issue = Issue.last(:order => 'id')
854 assert_equal IssueStatus.default, issue.status
928 assert_equal IssueStatus.default, issue.status
855 end
929 end
856
930
857 should "ignore unauthorized status" do
931 should "ignore unauthorized status" do
858 assert_difference 'Issue.count' do
932 assert_difference 'Issue.count' do
859 post :create, :project_id => 1,
933 post :create, :project_id => 1,
860 :issue => {:tracker_id => 1,
934 :issue => {:tracker_id => 1,
861 :subject => 'This is an issue',
935 :subject => 'This is an issue',
862 :status_id => 3}
936 :status_id => 3}
863 end
937 end
864 issue = Issue.last(:order => 'id')
938 issue = Issue.last(:order => 'id')
865 assert_equal IssueStatus.default, issue.status
939 assert_equal IssueStatus.default, issue.status
866 end
940 end
867 end
941 end
868
942
869 context "#update" do
943 context "#update" do
870 should "ignore status change" do
944 should "ignore status change" do
871 assert_difference 'Journal.count' do
945 assert_difference 'Journal.count' do
872 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
946 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
873 end
947 end
874 assert_equal 1, Issue.find(1).status_id
948 assert_equal 1, Issue.find(1).status_id
875 end
949 end
876
950
877 should "ignore attributes changes" do
951 should "ignore attributes changes" do
878 assert_difference 'Journal.count' do
952 assert_difference 'Journal.count' do
879 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
953 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
880 end
954 end
881 issue = Issue.find(1)
955 issue = Issue.find(1)
882 assert_equal "Can't print recipes", issue.subject
956 assert_equal "Can't print recipes", issue.subject
883 assert_nil issue.assigned_to
957 assert_nil issue.assigned_to
884 end
958 end
885 end
959 end
886 end
960 end
887
961
888 context "with workflow privilege" do
962 context "with workflow privilege" do
889 setup do
963 setup do
890 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
964 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
891 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
965 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
892 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
966 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
893 Role.anonymous.add_permission! :add_issues, :add_issue_notes
967 Role.anonymous.add_permission! :add_issues, :add_issue_notes
894 end
968 end
895
969
896 context "#update" do
970 context "#update" do
897 should "accept authorized status" do
971 should "accept authorized status" do
898 assert_difference 'Journal.count' do
972 assert_difference 'Journal.count' do
899 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
973 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
900 end
974 end
901 assert_equal 3, Issue.find(1).status_id
975 assert_equal 3, Issue.find(1).status_id
902 end
976 end
903
977
904 should "ignore unauthorized status" do
978 should "ignore unauthorized status" do
905 assert_difference 'Journal.count' do
979 assert_difference 'Journal.count' do
906 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
980 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
907 end
981 end
908 assert_equal 1, Issue.find(1).status_id
982 assert_equal 1, Issue.find(1).status_id
909 end
983 end
910
984
911 should "accept authorized attributes changes" do
985 should "accept authorized attributes changes" do
912 assert_difference 'Journal.count' do
986 assert_difference 'Journal.count' do
913 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
987 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
914 end
988 end
915 issue = Issue.find(1)
989 issue = Issue.find(1)
916 assert_equal 2, issue.assigned_to_id
990 assert_equal 2, issue.assigned_to_id
917 end
991 end
918
992
919 should "ignore unauthorized attributes changes" do
993 should "ignore unauthorized attributes changes" do
920 assert_difference 'Journal.count' do
994 assert_difference 'Journal.count' do
921 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
995 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
922 end
996 end
923 issue = Issue.find(1)
997 issue = Issue.find(1)
924 assert_equal "Can't print recipes", issue.subject
998 assert_equal "Can't print recipes", issue.subject
925 end
999 end
926 end
1000 end
927
1001
928 context "and :edit_issues permission" do
1002 context "and :edit_issues permission" do
929 setup do
1003 setup do
930 Role.anonymous.add_permission! :add_issues, :edit_issues
1004 Role.anonymous.add_permission! :add_issues, :edit_issues
931 end
1005 end
932
1006
933 should "accept authorized status" do
1007 should "accept authorized status" do
934 assert_difference 'Journal.count' do
1008 assert_difference 'Journal.count' do
935 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1009 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
936 end
1010 end
937 assert_equal 3, Issue.find(1).status_id
1011 assert_equal 3, Issue.find(1).status_id
938 end
1012 end
939
1013
940 should "ignore unauthorized status" do
1014 should "ignore unauthorized status" do
941 assert_difference 'Journal.count' do
1015 assert_difference 'Journal.count' do
942 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1016 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
943 end
1017 end
944 assert_equal 1, Issue.find(1).status_id
1018 assert_equal 1, Issue.find(1).status_id
945 end
1019 end
946
1020
947 should "accept authorized attributes changes" do
1021 should "accept authorized attributes changes" do
948 assert_difference 'Journal.count' do
1022 assert_difference 'Journal.count' do
949 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1023 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
950 end
1024 end
951 issue = Issue.find(1)
1025 issue = Issue.find(1)
952 assert_equal "changed", issue.subject
1026 assert_equal "changed", issue.subject
953 assert_equal 2, issue.assigned_to_id
1027 assert_equal 2, issue.assigned_to_id
954 end
1028 end
955 end
1029 end
956 end
1030 end
957
1031
958 def test_copy_issue
1032 def test_copy_issue
959 @request.session[:user_id] = 2
1033 @request.session[:user_id] = 2
960 get :new, :project_id => 1, :copy_from => 1
1034 get :new, :project_id => 1, :copy_from => 1
961 assert_template 'new'
1035 assert_template 'new'
962 assert_not_nil assigns(:issue)
1036 assert_not_nil assigns(:issue)
963 orig = Issue.find(1)
1037 orig = Issue.find(1)
964 assert_equal orig.subject, assigns(:issue).subject
1038 assert_equal orig.subject, assigns(:issue).subject
965 end
1039 end
966
1040
967 def test_get_edit
1041 def test_get_edit
968 @request.session[:user_id] = 2
1042 @request.session[:user_id] = 2
969 get :edit, :id => 1
1043 get :edit, :id => 1
970 assert_response :success
1044 assert_response :success
971 assert_template 'edit'
1045 assert_template 'edit'
972 assert_not_nil assigns(:issue)
1046 assert_not_nil assigns(:issue)
973 assert_equal Issue.find(1), assigns(:issue)
1047 assert_equal Issue.find(1), assigns(:issue)
974
1048
975 # Be sure we don't display inactive IssuePriorities
1049 # Be sure we don't display inactive IssuePriorities
976 assert ! IssuePriority.find(15).active?
1050 assert ! IssuePriority.find(15).active?
977 assert_no_tag :option, :attributes => {:value => '15'},
1051 assert_no_tag :option, :attributes => {:value => '15'},
978 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1052 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
979 end
1053 end
980
1054
981 def test_get_edit_with_params
1055 def test_get_edit_with_params
982 @request.session[:user_id] = 2
1056 @request.session[:user_id] = 2
983 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1057 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
984 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1058 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
985 assert_response :success
1059 assert_response :success
986 assert_template 'edit'
1060 assert_template 'edit'
987
1061
988 issue = assigns(:issue)
1062 issue = assigns(:issue)
989 assert_not_nil issue
1063 assert_not_nil issue
990
1064
991 assert_equal 5, issue.status_id
1065 assert_equal 5, issue.status_id
992 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1066 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
993 :child => { :tag => 'option',
1067 :child => { :tag => 'option',
994 :content => 'Closed',
1068 :content => 'Closed',
995 :attributes => { :selected => 'selected' } }
1069 :attributes => { :selected => 'selected' } }
996
1070
997 assert_equal 7, issue.priority_id
1071 assert_equal 7, issue.priority_id
998 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1072 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
999 :child => { :tag => 'option',
1073 :child => { :tag => 'option',
1000 :content => 'Urgent',
1074 :content => 'Urgent',
1001 :attributes => { :selected => 'selected' } }
1075 :attributes => { :selected => 'selected' } }
1002
1076
1003 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1077 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1004 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1078 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1005 :child => { :tag => 'option',
1079 :child => { :tag => 'option',
1006 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1080 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1007 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1081 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1008 end
1082 end
1009
1083
1010 def test_update_edit_form
1084 def test_update_edit_form
1011 @request.session[:user_id] = 2
1085 @request.session[:user_id] = 2
1012 xhr :post, :new, :project_id => 1,
1086 xhr :post, :new, :project_id => 1,
1013 :id => 1,
1087 :id => 1,
1014 :issue => {:tracker_id => 2,
1088 :issue => {:tracker_id => 2,
1015 :subject => 'This is the test_new issue',
1089 :subject => 'This is the test_new issue',
1016 :description => 'This is the description',
1090 :description => 'This is the description',
1017 :priority_id => 5}
1091 :priority_id => 5}
1018 assert_response :success
1092 assert_response :success
1019 assert_template 'attributes'
1093 assert_template 'attributes'
1020
1094
1021 issue = assigns(:issue)
1095 issue = assigns(:issue)
1022 assert_kind_of Issue, issue
1096 assert_kind_of Issue, issue
1023 assert_equal 1, issue.id
1097 assert_equal 1, issue.id
1024 assert_equal 1, issue.project_id
1098 assert_equal 1, issue.project_id
1025 assert_equal 2, issue.tracker_id
1099 assert_equal 2, issue.tracker_id
1026 assert_equal 'This is the test_new issue', issue.subject
1100 assert_equal 'This is the test_new issue', issue.subject
1027 end
1101 end
1028
1102
1029 def test_update_using_invalid_http_verbs
1103 def test_update_using_invalid_http_verbs
1030 @request.session[:user_id] = 2
1104 @request.session[:user_id] = 2
1031 subject = 'Updated by an invalid http verb'
1105 subject = 'Updated by an invalid http verb'
1032
1106
1033 get :update, :id => 1, :issue => {:subject => subject}
1107 get :update, :id => 1, :issue => {:subject => subject}
1034 assert_not_equal subject, Issue.find(1).subject
1108 assert_not_equal subject, Issue.find(1).subject
1035
1109
1036 post :update, :id => 1, :issue => {:subject => subject}
1110 post :update, :id => 1, :issue => {:subject => subject}
1037 assert_not_equal subject, Issue.find(1).subject
1111 assert_not_equal subject, Issue.find(1).subject
1038
1112
1039 delete :update, :id => 1, :issue => {:subject => subject}
1113 delete :update, :id => 1, :issue => {:subject => subject}
1040 assert_not_equal subject, Issue.find(1).subject
1114 assert_not_equal subject, Issue.find(1).subject
1041 end
1115 end
1042
1116
1043 def test_put_update_without_custom_fields_param
1117 def test_put_update_without_custom_fields_param
1044 @request.session[:user_id] = 2
1118 @request.session[:user_id] = 2
1045 ActionMailer::Base.deliveries.clear
1119 ActionMailer::Base.deliveries.clear
1046
1120
1047 issue = Issue.find(1)
1121 issue = Issue.find(1)
1048 assert_equal '125', issue.custom_value_for(2).value
1122 assert_equal '125', issue.custom_value_for(2).value
1049 old_subject = issue.subject
1123 old_subject = issue.subject
1050 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1124 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1051
1125
1052 assert_difference('Journal.count') do
1126 assert_difference('Journal.count') do
1053 assert_difference('JournalDetail.count', 2) do
1127 assert_difference('JournalDetail.count', 2) do
1054 put :update, :id => 1, :issue => {:subject => new_subject,
1128 put :update, :id => 1, :issue => {:subject => new_subject,
1055 :priority_id => '6',
1129 :priority_id => '6',
1056 :category_id => '1' # no change
1130 :category_id => '1' # no change
1057 }
1131 }
1058 end
1132 end
1059 end
1133 end
1060 assert_redirected_to :action => 'show', :id => '1'
1134 assert_redirected_to :action => 'show', :id => '1'
1061 issue.reload
1135 issue.reload
1062 assert_equal new_subject, issue.subject
1136 assert_equal new_subject, issue.subject
1063 # Make sure custom fields were not cleared
1137 # Make sure custom fields were not cleared
1064 assert_equal '125', issue.custom_value_for(2).value
1138 assert_equal '125', issue.custom_value_for(2).value
1065
1139
1066 mail = ActionMailer::Base.deliveries.last
1140 mail = ActionMailer::Base.deliveries.last
1067 assert_kind_of TMail::Mail, mail
1141 assert_kind_of TMail::Mail, mail
1068 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1142 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1069 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1143 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1070 end
1144 end
1071
1145
1072 def test_put_update_with_custom_field_change
1146 def test_put_update_with_custom_field_change
1073 @request.session[:user_id] = 2
1147 @request.session[:user_id] = 2
1074 issue = Issue.find(1)
1148 issue = Issue.find(1)
1075 assert_equal '125', issue.custom_value_for(2).value
1149 assert_equal '125', issue.custom_value_for(2).value
1076
1150
1077 assert_difference('Journal.count') do
1151 assert_difference('Journal.count') do
1078 assert_difference('JournalDetail.count', 3) do
1152 assert_difference('JournalDetail.count', 3) do
1079 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1153 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1080 :priority_id => '6',
1154 :priority_id => '6',
1081 :category_id => '1', # no change
1155 :category_id => '1', # no change
1082 :custom_field_values => { '2' => 'New custom value' }
1156 :custom_field_values => { '2' => 'New custom value' }
1083 }
1157 }
1084 end
1158 end
1085 end
1159 end
1086 assert_redirected_to :action => 'show', :id => '1'
1160 assert_redirected_to :action => 'show', :id => '1'
1087 issue.reload
1161 issue.reload
1088 assert_equal 'New custom value', issue.custom_value_for(2).value
1162 assert_equal 'New custom value', issue.custom_value_for(2).value
1089
1163
1090 mail = ActionMailer::Base.deliveries.last
1164 mail = ActionMailer::Base.deliveries.last
1091 assert_kind_of TMail::Mail, mail
1165 assert_kind_of TMail::Mail, mail
1092 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1166 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1093 end
1167 end
1094
1168
1095 def test_put_update_with_status_and_assignee_change
1169 def test_put_update_with_status_and_assignee_change
1096 issue = Issue.find(1)
1170 issue = Issue.find(1)
1097 assert_equal 1, issue.status_id
1171 assert_equal 1, issue.status_id
1098 @request.session[:user_id] = 2
1172 @request.session[:user_id] = 2
1099 assert_difference('TimeEntry.count', 0) do
1173 assert_difference('TimeEntry.count', 0) do
1100 put :update,
1174 put :update,
1101 :id => 1,
1175 :id => 1,
1102 :issue => { :status_id => 2, :assigned_to_id => 3 },
1176 :issue => { :status_id => 2, :assigned_to_id => 3 },
1103 :notes => 'Assigned to dlopper',
1177 :notes => 'Assigned to dlopper',
1104 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1178 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1105 end
1179 end
1106 assert_redirected_to :action => 'show', :id => '1'
1180 assert_redirected_to :action => 'show', :id => '1'
1107 issue.reload
1181 issue.reload
1108 assert_equal 2, issue.status_id
1182 assert_equal 2, issue.status_id
1109 j = Journal.find(:first, :order => 'id DESC')
1183 j = Journal.find(:first, :order => 'id DESC')
1110 assert_equal 'Assigned to dlopper', j.notes
1184 assert_equal 'Assigned to dlopper', j.notes
1111 assert_equal 2, j.details.size
1185 assert_equal 2, j.details.size
1112
1186
1113 mail = ActionMailer::Base.deliveries.last
1187 mail = ActionMailer::Base.deliveries.last
1114 assert mail.body.include?("Status changed from New to Assigned")
1188 assert mail.body.include?("Status changed from New to Assigned")
1115 # subject should contain the new status
1189 # subject should contain the new status
1116 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1190 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1117 end
1191 end
1118
1192
1119 def test_put_update_with_note_only
1193 def test_put_update_with_note_only
1120 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1194 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1121 # anonymous user
1195 # anonymous user
1122 put :update,
1196 put :update,
1123 :id => 1,
1197 :id => 1,
1124 :notes => notes
1198 :notes => notes
1125 assert_redirected_to :action => 'show', :id => '1'
1199 assert_redirected_to :action => 'show', :id => '1'
1126 j = Journal.find(:first, :order => 'id DESC')
1200 j = Journal.find(:first, :order => 'id DESC')
1127 assert_equal notes, j.notes
1201 assert_equal notes, j.notes
1128 assert_equal 0, j.details.size
1202 assert_equal 0, j.details.size
1129 assert_equal User.anonymous, j.user
1203 assert_equal User.anonymous, j.user
1130
1204
1131 mail = ActionMailer::Base.deliveries.last
1205 mail = ActionMailer::Base.deliveries.last
1132 assert mail.body.include?(notes)
1206 assert mail.body.include?(notes)
1133 end
1207 end
1134
1208
1135 def test_put_update_with_note_and_spent_time
1209 def test_put_update_with_note_and_spent_time
1136 @request.session[:user_id] = 2
1210 @request.session[:user_id] = 2
1137 spent_hours_before = Issue.find(1).spent_hours
1211 spent_hours_before = Issue.find(1).spent_hours
1138 assert_difference('TimeEntry.count') do
1212 assert_difference('TimeEntry.count') do
1139 put :update,
1213 put :update,
1140 :id => 1,
1214 :id => 1,
1141 :notes => '2.5 hours added',
1215 :notes => '2.5 hours added',
1142 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1216 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1143 end
1217 end
1144 assert_redirected_to :action => 'show', :id => '1'
1218 assert_redirected_to :action => 'show', :id => '1'
1145
1219
1146 issue = Issue.find(1)
1220 issue = Issue.find(1)
1147
1221
1148 j = Journal.find(:first, :order => 'id DESC')
1222 j = Journal.find(:first, :order => 'id DESC')
1149 assert_equal '2.5 hours added', j.notes
1223 assert_equal '2.5 hours added', j.notes
1150 assert_equal 0, j.details.size
1224 assert_equal 0, j.details.size
1151
1225
1152 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1226 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1153 assert_not_nil t
1227 assert_not_nil t
1154 assert_equal 2.5, t.hours
1228 assert_equal 2.5, t.hours
1155 assert_equal spent_hours_before + 2.5, issue.spent_hours
1229 assert_equal spent_hours_before + 2.5, issue.spent_hours
1156 end
1230 end
1157
1231
1158 def test_put_update_with_attachment_only
1232 def test_put_update_with_attachment_only
1159 set_tmp_attachments_directory
1233 set_tmp_attachments_directory
1160
1234
1161 # Delete all fixtured journals, a race condition can occur causing the wrong
1235 # Delete all fixtured journals, a race condition can occur causing the wrong
1162 # journal to get fetched in the next find.
1236 # journal to get fetched in the next find.
1163 Journal.delete_all
1237 Journal.delete_all
1164
1238
1165 # anonymous user
1239 # anonymous user
1166 assert_difference 'Attachment.count' do
1240 assert_difference 'Attachment.count' do
1167 put :update, :id => 1,
1241 put :update, :id => 1,
1168 :notes => '',
1242 :notes => '',
1169 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1243 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1170 end
1244 end
1171
1245
1172 assert_redirected_to :action => 'show', :id => '1'
1246 assert_redirected_to :action => 'show', :id => '1'
1173 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1247 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1174 assert j.notes.blank?
1248 assert j.notes.blank?
1175 assert_equal 1, j.details.size
1249 assert_equal 1, j.details.size
1176 assert_equal 'testfile.txt', j.details.first.value
1250 assert_equal 'testfile.txt', j.details.first.value
1177 assert_equal User.anonymous, j.user
1251 assert_equal User.anonymous, j.user
1178
1252
1179 attachment = Attachment.first(:order => 'id DESC')
1253 attachment = Attachment.first(:order => 'id DESC')
1180 assert_equal Issue.find(1), attachment.container
1254 assert_equal Issue.find(1), attachment.container
1181 assert_equal User.anonymous, attachment.author
1255 assert_equal User.anonymous, attachment.author
1182 assert_equal 'testfile.txt', attachment.filename
1256 assert_equal 'testfile.txt', attachment.filename
1183 assert_equal 'text/plain', attachment.content_type
1257 assert_equal 'text/plain', attachment.content_type
1184 assert_equal 'test file', attachment.description
1258 assert_equal 'test file', attachment.description
1185 assert_equal 59, attachment.filesize
1259 assert_equal 59, attachment.filesize
1186 assert File.exists?(attachment.diskfile)
1260 assert File.exists?(attachment.diskfile)
1187 assert_equal 59, File.size(attachment.diskfile)
1261 assert_equal 59, File.size(attachment.diskfile)
1188
1262
1189 mail = ActionMailer::Base.deliveries.last
1263 mail = ActionMailer::Base.deliveries.last
1190 assert mail.body.include?('testfile.txt')
1264 assert mail.body.include?('testfile.txt')
1191 end
1265 end
1192
1266
1193 def test_put_update_with_attachment_that_fails_to_save
1267 def test_put_update_with_attachment_that_fails_to_save
1194 set_tmp_attachments_directory
1268 set_tmp_attachments_directory
1195
1269
1196 # Delete all fixtured journals, a race condition can occur causing the wrong
1270 # Delete all fixtured journals, a race condition can occur causing the wrong
1197 # journal to get fetched in the next find.
1271 # journal to get fetched in the next find.
1198 Journal.delete_all
1272 Journal.delete_all
1199
1273
1200 # Mock out the unsaved attachment
1274 # Mock out the unsaved attachment
1201 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1275 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1202
1276
1203 # anonymous user
1277 # anonymous user
1204 put :update,
1278 put :update,
1205 :id => 1,
1279 :id => 1,
1206 :notes => '',
1280 :notes => '',
1207 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1281 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1208 assert_redirected_to :action => 'show', :id => '1'
1282 assert_redirected_to :action => 'show', :id => '1'
1209 assert_equal '1 file(s) could not be saved.', flash[:warning]
1283 assert_equal '1 file(s) could not be saved.', flash[:warning]
1210
1284
1211 end if Object.const_defined?(:Mocha)
1285 end if Object.const_defined?(:Mocha)
1212
1286
1213 def test_put_update_with_no_change
1287 def test_put_update_with_no_change
1214 issue = Issue.find(1)
1288 issue = Issue.find(1)
1215 issue.journals.clear
1289 issue.journals.clear
1216 ActionMailer::Base.deliveries.clear
1290 ActionMailer::Base.deliveries.clear
1217
1291
1218 put :update,
1292 put :update,
1219 :id => 1,
1293 :id => 1,
1220 :notes => ''
1294 :notes => ''
1221 assert_redirected_to :action => 'show', :id => '1'
1295 assert_redirected_to :action => 'show', :id => '1'
1222
1296
1223 issue.reload
1297 issue.reload
1224 assert issue.journals.empty?
1298 assert issue.journals.empty?
1225 # No email should be sent
1299 # No email should be sent
1226 assert ActionMailer::Base.deliveries.empty?
1300 assert ActionMailer::Base.deliveries.empty?
1227 end
1301 end
1228
1302
1229 def test_put_update_should_send_a_notification
1303 def test_put_update_should_send_a_notification
1230 @request.session[:user_id] = 2
1304 @request.session[:user_id] = 2
1231 ActionMailer::Base.deliveries.clear
1305 ActionMailer::Base.deliveries.clear
1232 issue = Issue.find(1)
1306 issue = Issue.find(1)
1233 old_subject = issue.subject
1307 old_subject = issue.subject
1234 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1308 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1235
1309
1236 put :update, :id => 1, :issue => {:subject => new_subject,
1310 put :update, :id => 1, :issue => {:subject => new_subject,
1237 :priority_id => '6',
1311 :priority_id => '6',
1238 :category_id => '1' # no change
1312 :category_id => '1' # no change
1239 }
1313 }
1240 assert_equal 1, ActionMailer::Base.deliveries.size
1314 assert_equal 1, ActionMailer::Base.deliveries.size
1241 end
1315 end
1242
1316
1243 def test_put_update_with_invalid_spent_time_hours_only
1317 def test_put_update_with_invalid_spent_time_hours_only
1244 @request.session[:user_id] = 2
1318 @request.session[:user_id] = 2
1245 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1319 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1246
1320
1247 assert_no_difference('Journal.count') do
1321 assert_no_difference('Journal.count') do
1248 put :update,
1322 put :update,
1249 :id => 1,
1323 :id => 1,
1250 :notes => notes,
1324 :notes => notes,
1251 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1325 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1252 end
1326 end
1253 assert_response :success
1327 assert_response :success
1254 assert_template 'edit'
1328 assert_template 'edit'
1255
1329
1256 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1330 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1257 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1331 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1258 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1332 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1259 end
1333 end
1260
1334
1261 def test_put_update_with_invalid_spent_time_comments_only
1335 def test_put_update_with_invalid_spent_time_comments_only
1262 @request.session[:user_id] = 2
1336 @request.session[:user_id] = 2
1263 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1337 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1264
1338
1265 assert_no_difference('Journal.count') do
1339 assert_no_difference('Journal.count') do
1266 put :update,
1340 put :update,
1267 :id => 1,
1341 :id => 1,
1268 :notes => notes,
1342 :notes => notes,
1269 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1343 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1270 end
1344 end
1271 assert_response :success
1345 assert_response :success
1272 assert_template 'edit'
1346 assert_template 'edit'
1273
1347
1274 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1348 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1275 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1349 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1276 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1350 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1277 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1351 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1278 end
1352 end
1279
1353
1280 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1354 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1281 issue = Issue.find(2)
1355 issue = Issue.find(2)
1282 @request.session[:user_id] = 2
1356 @request.session[:user_id] = 2
1283
1357
1284 put :update,
1358 put :update,
1285 :id => issue.id,
1359 :id => issue.id,
1286 :issue => {
1360 :issue => {
1287 :fixed_version_id => 4
1361 :fixed_version_id => 4
1288 }
1362 }
1289
1363
1290 assert_response :redirect
1364 assert_response :redirect
1291 issue.reload
1365 issue.reload
1292 assert_equal 4, issue.fixed_version_id
1366 assert_equal 4, issue.fixed_version_id
1293 assert_not_equal issue.project_id, issue.fixed_version.project_id
1367 assert_not_equal issue.project_id, issue.fixed_version.project_id
1294 end
1368 end
1295
1369
1296 def test_put_update_should_redirect_back_using_the_back_url_parameter
1370 def test_put_update_should_redirect_back_using_the_back_url_parameter
1297 issue = Issue.find(2)
1371 issue = Issue.find(2)
1298 @request.session[:user_id] = 2
1372 @request.session[:user_id] = 2
1299
1373
1300 put :update,
1374 put :update,
1301 :id => issue.id,
1375 :id => issue.id,
1302 :issue => {
1376 :issue => {
1303 :fixed_version_id => 4
1377 :fixed_version_id => 4
1304 },
1378 },
1305 :back_url => '/issues'
1379 :back_url => '/issues'
1306
1380
1307 assert_response :redirect
1381 assert_response :redirect
1308 assert_redirected_to '/issues'
1382 assert_redirected_to '/issues'
1309 end
1383 end
1310
1384
1311 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1385 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1312 issue = Issue.find(2)
1386 issue = Issue.find(2)
1313 @request.session[:user_id] = 2
1387 @request.session[:user_id] = 2
1314
1388
1315 put :update,
1389 put :update,
1316 :id => issue.id,
1390 :id => issue.id,
1317 :issue => {
1391 :issue => {
1318 :fixed_version_id => 4
1392 :fixed_version_id => 4
1319 },
1393 },
1320 :back_url => 'http://google.com'
1394 :back_url => 'http://google.com'
1321
1395
1322 assert_response :redirect
1396 assert_response :redirect
1323 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1397 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1324 end
1398 end
1325
1399
1326 def test_get_bulk_edit
1400 def test_get_bulk_edit
1327 @request.session[:user_id] = 2
1401 @request.session[:user_id] = 2
1328 get :bulk_edit, :ids => [1, 2]
1402 get :bulk_edit, :ids => [1, 2]
1329 assert_response :success
1403 assert_response :success
1330 assert_template 'bulk_edit'
1404 assert_template 'bulk_edit'
1331
1405
1332 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1406 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1333
1407
1334 # Project specific custom field, date type
1408 # Project specific custom field, date type
1335 field = CustomField.find(9)
1409 field = CustomField.find(9)
1336 assert !field.is_for_all?
1410 assert !field.is_for_all?
1337 assert_equal 'date', field.field_format
1411 assert_equal 'date', field.field_format
1338 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1412 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1339
1413
1340 # System wide custom field
1414 # System wide custom field
1341 assert CustomField.find(1).is_for_all?
1415 assert CustomField.find(1).is_for_all?
1342 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1416 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1343
1417
1344 # Be sure we don't display inactive IssuePriorities
1418 # Be sure we don't display inactive IssuePriorities
1345 assert ! IssuePriority.find(15).active?
1419 assert ! IssuePriority.find(15).active?
1346 assert_no_tag :option, :attributes => {:value => '15'},
1420 assert_no_tag :option, :attributes => {:value => '15'},
1347 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1421 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1348 end
1422 end
1349
1423
1350 def test_get_bulk_edit_on_different_projects
1424 def test_get_bulk_edit_on_different_projects
1351 @request.session[:user_id] = 2
1425 @request.session[:user_id] = 2
1352 get :bulk_edit, :ids => [1, 2, 6]
1426 get :bulk_edit, :ids => [1, 2, 6]
1353 assert_response :success
1427 assert_response :success
1354 assert_template 'bulk_edit'
1428 assert_template 'bulk_edit'
1355
1429
1356 # Can not set issues from different projects as children of an issue
1430 # Can not set issues from different projects as children of an issue
1357 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1431 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1358
1432
1359 # Project specific custom field, date type
1433 # Project specific custom field, date type
1360 field = CustomField.find(9)
1434 field = CustomField.find(9)
1361 assert !field.is_for_all?
1435 assert !field.is_for_all?
1362 assert !field.project_ids.include?(Issue.find(6).project_id)
1436 assert !field.project_ids.include?(Issue.find(6).project_id)
1363 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1437 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1364 end
1438 end
1365
1439
1366 def test_get_bulk_edit_with_user_custom_field
1440 def test_get_bulk_edit_with_user_custom_field
1367 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1441 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1368
1442
1369 @request.session[:user_id] = 2
1443 @request.session[:user_id] = 2
1370 get :bulk_edit, :ids => [1, 2]
1444 get :bulk_edit, :ids => [1, 2]
1371 assert_response :success
1445 assert_response :success
1372 assert_template 'bulk_edit'
1446 assert_template 'bulk_edit'
1373
1447
1374 assert_tag :select,
1448 assert_tag :select,
1375 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1449 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1376 :children => {
1450 :children => {
1377 :only => {:tag => 'option'},
1451 :only => {:tag => 'option'},
1378 :count => Project.find(1).users.count + 1
1452 :count => Project.find(1).users.count + 1
1379 }
1453 }
1380 end
1454 end
1381
1455
1382 def test_get_bulk_edit_with_version_custom_field
1456 def test_get_bulk_edit_with_version_custom_field
1383 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1457 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1384
1458
1385 @request.session[:user_id] = 2
1459 @request.session[:user_id] = 2
1386 get :bulk_edit, :ids => [1, 2]
1460 get :bulk_edit, :ids => [1, 2]
1387 assert_response :success
1461 assert_response :success
1388 assert_template 'bulk_edit'
1462 assert_template 'bulk_edit'
1389
1463
1390 assert_tag :select,
1464 assert_tag :select,
1391 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1465 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1392 :children => {
1466 :children => {
1393 :only => {:tag => 'option'},
1467 :only => {:tag => 'option'},
1394 :count => Project.find(1).versions.count + 1
1468 :count => Project.find(1).versions.count + 1
1395 }
1469 }
1396 end
1470 end
1397
1471
1398 def test_bulk_update
1472 def test_bulk_update
1399 @request.session[:user_id] = 2
1473 @request.session[:user_id] = 2
1400 # update issues priority
1474 # update issues priority
1401 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1475 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1402 :issue => {:priority_id => 7,
1476 :issue => {:priority_id => 7,
1403 :assigned_to_id => '',
1477 :assigned_to_id => '',
1404 :custom_field_values => {'2' => ''}}
1478 :custom_field_values => {'2' => ''}}
1405
1479
1406 assert_response 302
1480 assert_response 302
1407 # check that the issues were updated
1481 # check that the issues were updated
1408 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1482 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1409
1483
1410 issue = Issue.find(1)
1484 issue = Issue.find(1)
1411 journal = issue.journals.find(:first, :order => 'created_on DESC')
1485 journal = issue.journals.find(:first, :order => 'created_on DESC')
1412 assert_equal '125', issue.custom_value_for(2).value
1486 assert_equal '125', issue.custom_value_for(2).value
1413 assert_equal 'Bulk editing', journal.notes
1487 assert_equal 'Bulk editing', journal.notes
1414 assert_equal 1, journal.details.size
1488 assert_equal 1, journal.details.size
1415 end
1489 end
1416
1490
1417 def test_bulk_update_with_group_assignee
1491 def test_bulk_update_with_group_assignee
1418 group = Group.find(11)
1492 group = Group.find(11)
1419 project = Project.find(1)
1493 project = Project.find(1)
1420 project.members << Member.new(:principal => group, :roles => [Role.first])
1494 project.members << Member.new(:principal => group, :roles => [Role.first])
1421
1495
1422 @request.session[:user_id] = 2
1496 @request.session[:user_id] = 2
1423 # update issues assignee
1497 # update issues assignee
1424 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1498 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1425 :issue => {:priority_id => '',
1499 :issue => {:priority_id => '',
1426 :assigned_to_id => group.id,
1500 :assigned_to_id => group.id,
1427 :custom_field_values => {'2' => ''}}
1501 :custom_field_values => {'2' => ''}}
1428
1502
1429 assert_response 302
1503 assert_response 302
1430 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
1504 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
1431 end
1505 end
1432
1506
1433 def test_bulk_update_on_different_projects
1507 def test_bulk_update_on_different_projects
1434 @request.session[:user_id] = 2
1508 @request.session[:user_id] = 2
1435 # update issues priority
1509 # update issues priority
1436 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1510 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1437 :issue => {:priority_id => 7,
1511 :issue => {:priority_id => 7,
1438 :assigned_to_id => '',
1512 :assigned_to_id => '',
1439 :custom_field_values => {'2' => ''}}
1513 :custom_field_values => {'2' => ''}}
1440
1514
1441 assert_response 302
1515 assert_response 302
1442 # check that the issues were updated
1516 # check that the issues were updated
1443 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1517 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1444
1518
1445 issue = Issue.find(1)
1519 issue = Issue.find(1)
1446 journal = issue.journals.find(:first, :order => 'created_on DESC')
1520 journal = issue.journals.find(:first, :order => 'created_on DESC')
1447 assert_equal '125', issue.custom_value_for(2).value
1521 assert_equal '125', issue.custom_value_for(2).value
1448 assert_equal 'Bulk editing', journal.notes
1522 assert_equal 'Bulk editing', journal.notes
1449 assert_equal 1, journal.details.size
1523 assert_equal 1, journal.details.size
1450 end
1524 end
1451
1525
1452 def test_bulk_update_on_different_projects_without_rights
1526 def test_bulk_update_on_different_projects_without_rights
1453 @request.session[:user_id] = 3
1527 @request.session[:user_id] = 3
1454 user = User.find(3)
1528 user = User.find(3)
1455 action = { :controller => "issues", :action => "bulk_update" }
1529 action = { :controller => "issues", :action => "bulk_update" }
1456 assert user.allowed_to?(action, Issue.find(1).project)
1530 assert user.allowed_to?(action, Issue.find(1).project)
1457 assert ! user.allowed_to?(action, Issue.find(6).project)
1531 assert ! user.allowed_to?(action, Issue.find(6).project)
1458 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1532 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1459 :issue => {:priority_id => 7,
1533 :issue => {:priority_id => 7,
1460 :assigned_to_id => '',
1534 :assigned_to_id => '',
1461 :custom_field_values => {'2' => ''}}
1535 :custom_field_values => {'2' => ''}}
1462 assert_response 403
1536 assert_response 403
1463 assert_not_equal "Bulk should fail", Journal.last.notes
1537 assert_not_equal "Bulk should fail", Journal.last.notes
1464 end
1538 end
1465
1539
1466 def test_bullk_update_should_send_a_notification
1540 def test_bullk_update_should_send_a_notification
1467 @request.session[:user_id] = 2
1541 @request.session[:user_id] = 2
1468 ActionMailer::Base.deliveries.clear
1542 ActionMailer::Base.deliveries.clear
1469 post(:bulk_update,
1543 post(:bulk_update,
1470 {
1544 {
1471 :ids => [1, 2],
1545 :ids => [1, 2],
1472 :notes => 'Bulk editing',
1546 :notes => 'Bulk editing',
1473 :issue => {
1547 :issue => {
1474 :priority_id => 7,
1548 :priority_id => 7,
1475 :assigned_to_id => '',
1549 :assigned_to_id => '',
1476 :custom_field_values => {'2' => ''}
1550 :custom_field_values => {'2' => ''}
1477 }
1551 }
1478 })
1552 })
1479
1553
1480 assert_response 302
1554 assert_response 302
1481 assert_equal 2, ActionMailer::Base.deliveries.size
1555 assert_equal 2, ActionMailer::Base.deliveries.size
1482 end
1556 end
1483
1557
1484 def test_bulk_update_status
1558 def test_bulk_update_status
1485 @request.session[:user_id] = 2
1559 @request.session[:user_id] = 2
1486 # update issues priority
1560 # update issues priority
1487 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1561 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1488 :issue => {:priority_id => '',
1562 :issue => {:priority_id => '',
1489 :assigned_to_id => '',
1563 :assigned_to_id => '',
1490 :status_id => '5'}
1564 :status_id => '5'}
1491
1565
1492 assert_response 302
1566 assert_response 302
1493 issue = Issue.find(1)
1567 issue = Issue.find(1)
1494 assert issue.closed?
1568 assert issue.closed?
1495 end
1569 end
1496
1570
1497 def test_bulk_update_parent_id
1571 def test_bulk_update_parent_id
1498 @request.session[:user_id] = 2
1572 @request.session[:user_id] = 2
1499 post :bulk_update, :ids => [1, 3],
1573 post :bulk_update, :ids => [1, 3],
1500 :notes => 'Bulk editing parent',
1574 :notes => 'Bulk editing parent',
1501 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1575 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1502
1576
1503 assert_response 302
1577 assert_response 302
1504 parent = Issue.find(2)
1578 parent = Issue.find(2)
1505 assert_equal parent.id, Issue.find(1).parent_id
1579 assert_equal parent.id, Issue.find(1).parent_id
1506 assert_equal parent.id, Issue.find(3).parent_id
1580 assert_equal parent.id, Issue.find(3).parent_id
1507 assert_equal [1, 3], parent.children.collect(&:id).sort
1581 assert_equal [1, 3], parent.children.collect(&:id).sort
1508 end
1582 end
1509
1583
1510 def test_bulk_update_custom_field
1584 def test_bulk_update_custom_field
1511 @request.session[:user_id] = 2
1585 @request.session[:user_id] = 2
1512 # update issues priority
1586 # update issues priority
1513 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1587 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1514 :issue => {:priority_id => '',
1588 :issue => {:priority_id => '',
1515 :assigned_to_id => '',
1589 :assigned_to_id => '',
1516 :custom_field_values => {'2' => '777'}}
1590 :custom_field_values => {'2' => '777'}}
1517
1591
1518 assert_response 302
1592 assert_response 302
1519
1593
1520 issue = Issue.find(1)
1594 issue = Issue.find(1)
1521 journal = issue.journals.find(:first, :order => 'created_on DESC')
1595 journal = issue.journals.find(:first, :order => 'created_on DESC')
1522 assert_equal '777', issue.custom_value_for(2).value
1596 assert_equal '777', issue.custom_value_for(2).value
1523 assert_equal 1, journal.details.size
1597 assert_equal 1, journal.details.size
1524 assert_equal '125', journal.details.first.old_value
1598 assert_equal '125', journal.details.first.old_value
1525 assert_equal '777', journal.details.first.value
1599 assert_equal '777', journal.details.first.value
1526 end
1600 end
1527
1601
1528 def test_bulk_update_unassign
1602 def test_bulk_update_unassign
1529 assert_not_nil Issue.find(2).assigned_to
1603 assert_not_nil Issue.find(2).assigned_to
1530 @request.session[:user_id] = 2
1604 @request.session[:user_id] = 2
1531 # unassign issues
1605 # unassign issues
1532 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1606 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1533 assert_response 302
1607 assert_response 302
1534 # check that the issues were updated
1608 # check that the issues were updated
1535 assert_nil Issue.find(2).assigned_to
1609 assert_nil Issue.find(2).assigned_to
1536 end
1610 end
1537
1611
1538 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1612 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1539 @request.session[:user_id] = 2
1613 @request.session[:user_id] = 2
1540
1614
1541 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1615 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1542
1616
1543 assert_response :redirect
1617 assert_response :redirect
1544 issues = Issue.find([1,2])
1618 issues = Issue.find([1,2])
1545 issues.each do |issue|
1619 issues.each do |issue|
1546 assert_equal 4, issue.fixed_version_id
1620 assert_equal 4, issue.fixed_version_id
1547 assert_not_equal issue.project_id, issue.fixed_version.project_id
1621 assert_not_equal issue.project_id, issue.fixed_version.project_id
1548 end
1622 end
1549 end
1623 end
1550
1624
1551 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1625 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1552 @request.session[:user_id] = 2
1626 @request.session[:user_id] = 2
1553 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1627 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1554
1628
1555 assert_response :redirect
1629 assert_response :redirect
1556 assert_redirected_to '/issues'
1630 assert_redirected_to '/issues'
1557 end
1631 end
1558
1632
1559 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1633 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1560 @request.session[:user_id] = 2
1634 @request.session[:user_id] = 2
1561 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1635 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1562
1636
1563 assert_response :redirect
1637 assert_response :redirect
1564 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1638 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1565 end
1639 end
1566
1640
1567 def test_destroy_issue_with_no_time_entries
1641 def test_destroy_issue_with_no_time_entries
1568 assert_nil TimeEntry.find_by_issue_id(2)
1642 assert_nil TimeEntry.find_by_issue_id(2)
1569 @request.session[:user_id] = 2
1643 @request.session[:user_id] = 2
1570 post :destroy, :id => 2
1644 post :destroy, :id => 2
1571 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1645 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1572 assert_nil Issue.find_by_id(2)
1646 assert_nil Issue.find_by_id(2)
1573 end
1647 end
1574
1648
1575 def test_destroy_issues_with_time_entries
1649 def test_destroy_issues_with_time_entries
1576 @request.session[:user_id] = 2
1650 @request.session[:user_id] = 2
1577 post :destroy, :ids => [1, 3]
1651 post :destroy, :ids => [1, 3]
1578 assert_response :success
1652 assert_response :success
1579 assert_template 'destroy'
1653 assert_template 'destroy'
1580 assert_not_nil assigns(:hours)
1654 assert_not_nil assigns(:hours)
1581 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1655 assert Issue.find_by_id(1) && Issue.find_by_id(3)
1582 end
1656 end
1583
1657
1584 def test_destroy_issues_and_destroy_time_entries
1658 def test_destroy_issues_and_destroy_time_entries
1585 @request.session[:user_id] = 2
1659 @request.session[:user_id] = 2
1586 post :destroy, :ids => [1, 3], :todo => 'destroy'
1660 post :destroy, :ids => [1, 3], :todo => 'destroy'
1587 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1661 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1588 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1662 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1589 assert_nil TimeEntry.find_by_id([1, 2])
1663 assert_nil TimeEntry.find_by_id([1, 2])
1590 end
1664 end
1591
1665
1592 def test_destroy_issues_and_assign_time_entries_to_project
1666 def test_destroy_issues_and_assign_time_entries_to_project
1593 @request.session[:user_id] = 2
1667 @request.session[:user_id] = 2
1594 post :destroy, :ids => [1, 3], :todo => 'nullify'
1668 post :destroy, :ids => [1, 3], :todo => 'nullify'
1595 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1669 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1596 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1670 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1597 assert_nil TimeEntry.find(1).issue_id
1671 assert_nil TimeEntry.find(1).issue_id
1598 assert_nil TimeEntry.find(2).issue_id
1672 assert_nil TimeEntry.find(2).issue_id
1599 end
1673 end
1600
1674
1601 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1675 def test_destroy_issues_and_reassign_time_entries_to_another_issue
1602 @request.session[:user_id] = 2
1676 @request.session[:user_id] = 2
1603 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1677 post :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
1604 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1678 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
1605 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1679 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
1606 assert_equal 2, TimeEntry.find(1).issue_id
1680 assert_equal 2, TimeEntry.find(1).issue_id
1607 assert_equal 2, TimeEntry.find(2).issue_id
1681 assert_equal 2, TimeEntry.find(2).issue_id
1608 end
1682 end
1609
1683
1610 def test_destroy_issues_from_different_projects
1684 def test_destroy_issues_from_different_projects
1611 @request.session[:user_id] = 2
1685 @request.session[:user_id] = 2
1612 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1686 post :destroy, :ids => [1, 2, 6], :todo => 'destroy'
1613 assert_redirected_to :controller => 'issues', :action => 'index'
1687 assert_redirected_to :controller => 'issues', :action => 'index'
1614 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1688 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
1615 end
1689 end
1616
1690
1617 def test_destroy_parent_and_child_issues
1691 def test_destroy_parent_and_child_issues
1618 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
1692 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
1619 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
1693 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
1620 assert child.is_descendant_of?(parent.reload)
1694 assert child.is_descendant_of?(parent.reload)
1621
1695
1622 @request.session[:user_id] = 2
1696 @request.session[:user_id] = 2
1623 assert_difference 'Issue.count', -2 do
1697 assert_difference 'Issue.count', -2 do
1624 post :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
1698 post :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
1625 end
1699 end
1626 assert_response 302
1700 assert_response 302
1627 end
1701 end
1628
1702
1629 def test_default_search_scope
1703 def test_default_search_scope
1630 get :index
1704 get :index
1631 assert_tag :div, :attributes => {:id => 'quick-search'},
1705 assert_tag :div, :attributes => {:id => 'quick-search'},
1632 :child => {:tag => 'form',
1706 :child => {:tag => 'form',
1633 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1707 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
1634 end
1708 end
1635 end
1709 end
General Comments 0
You need to be logged in to leave comments. Login now