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