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