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