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