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