##// END OF EJS Templates
Fixed that entering #nnn as parent task should validate (#11979)....
Jean-Philippe Lang -
r10447:4b6b56863573
parent child
Show More
@@ -1,1360 +1,1361
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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 Issue < ActiveRecord::Base
19 19 include Redmine::SafeAttributes
20 20
21 21 belongs_to :project
22 22 belongs_to :tracker
23 23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
24 24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
25 25 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
26 26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
27 27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
28 28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
29 29
30 30 has_many :journals, :as => :journalized, :dependent => :destroy
31 31 has_many :visible_journals,
32 32 :class_name => 'Journal',
33 33 :as => :journalized,
34 34 :conditions => Proc.new {
35 35 ["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
36 36 },
37 37 :readonly => true
38 38
39 39 has_many :time_entries, :dependent => :delete_all
40 40 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
41 41
42 42 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
43 43 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
44 44
45 45 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
46 46 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
47 47 acts_as_customizable
48 48 acts_as_watchable
49 49 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
50 50 :include => [:project, :visible_journals],
51 51 # sort by id so that limited eager loading doesn't break with postgresql
52 52 :order_column => "#{table_name}.id"
53 53 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
54 54 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
55 55 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
56 56
57 57 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
58 58 :author_key => :author_id
59 59
60 60 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
61 61
62 62 attr_reader :current_journal
63 63 delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
64 64
65 65 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
66 66
67 67 validates_length_of :subject, :maximum => 255
68 68 validates_inclusion_of :done_ratio, :in => 0..100
69 69 validates_numericality_of :estimated_hours, :allow_nil => true
70 70 validate :validate_issue, :validate_required_fields
71 71
72 72 scope :visible,
73 73 lambda {|*args| { :include => :project,
74 74 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
75 75
76 76 scope :open, lambda {|*args|
77 77 is_closed = args.size > 0 ? !args.first : false
78 78 {:conditions => ["#{IssueStatus.table_name}.is_closed = ?", is_closed], :include => :status}
79 79 }
80 80
81 81 scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
82 82 scope :on_active_project, :include => [:status, :project, :tracker],
83 83 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
84 84
85 85 before_create :default_assign
86 86 before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change
87 87 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
88 88 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
89 89 # Should be after_create but would be called before previous after_save callbacks
90 90 after_save :after_create_from_copy
91 91 after_destroy :update_parent_attributes
92 92
93 93 # Returns a SQL conditions string used to find all issues visible by the specified user
94 94 def self.visible_condition(user, options={})
95 95 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
96 96 if user.logged?
97 97 case role.issues_visibility
98 98 when 'all'
99 99 nil
100 100 when 'default'
101 101 user_ids = [user.id] + user.groups.map(&:id)
102 102 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
103 103 when 'own'
104 104 user_ids = [user.id] + user.groups.map(&:id)
105 105 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
106 106 else
107 107 '1=0'
108 108 end
109 109 else
110 110 "(#{table_name}.is_private = #{connection.quoted_false})"
111 111 end
112 112 end
113 113 end
114 114
115 115 # Returns true if usr or current user is allowed to view the issue
116 116 def visible?(usr=nil)
117 117 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
118 118 if user.logged?
119 119 case role.issues_visibility
120 120 when 'all'
121 121 true
122 122 when 'default'
123 123 !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
124 124 when 'own'
125 125 self.author == user || user.is_or_belongs_to?(assigned_to)
126 126 else
127 127 false
128 128 end
129 129 else
130 130 !self.is_private?
131 131 end
132 132 end
133 133 end
134 134
135 135 def initialize(attributes=nil, *args)
136 136 super
137 137 if new_record?
138 138 # set default values for new records only
139 139 self.status ||= IssueStatus.default
140 140 self.priority ||= IssuePriority.default
141 141 self.watcher_user_ids = []
142 142 end
143 143 end
144 144
145 145 # AR#Persistence#destroy would raise and RecordNotFound exception
146 146 # if the issue was already deleted or updated (non matching lock_version).
147 147 # This is a problem when bulk deleting issues or deleting a project
148 148 # (because an issue may already be deleted if its parent was deleted
149 149 # first).
150 150 # The issue is reloaded by the nested_set before being deleted so
151 151 # the lock_version condition should not be an issue but we handle it.
152 152 def destroy
153 153 super
154 154 rescue ActiveRecord::RecordNotFound
155 155 # Stale or already deleted
156 156 begin
157 157 reload
158 158 rescue ActiveRecord::RecordNotFound
159 159 # The issue was actually already deleted
160 160 @destroyed = true
161 161 return freeze
162 162 end
163 163 # The issue was stale, retry to destroy
164 164 super
165 165 end
166 166
167 167 def reload(*args)
168 168 @workflow_rule_by_attribute = nil
169 169 @assignable_versions = nil
170 170 super
171 171 end
172 172
173 173 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
174 174 def available_custom_fields
175 175 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
176 176 end
177 177
178 178 # Copies attributes from another issue, arg can be an id or an Issue
179 179 def copy_from(arg, options={})
180 180 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
181 181 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
182 182 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
183 183 self.status = issue.status
184 184 self.author = User.current
185 185 unless options[:attachments] == false
186 186 self.attachments = issue.attachments.map do |attachement|
187 187 attachement.copy(:container => self)
188 188 end
189 189 end
190 190 @copied_from = issue
191 191 @copy_options = options
192 192 self
193 193 end
194 194
195 195 # Returns an unsaved copy of the issue
196 196 def copy(attributes=nil, copy_options={})
197 197 copy = self.class.new.copy_from(self, copy_options)
198 198 copy.attributes = attributes if attributes
199 199 copy
200 200 end
201 201
202 202 # Returns true if the issue is a copy
203 203 def copy?
204 204 @copied_from.present?
205 205 end
206 206
207 207 # Moves/copies an issue to a new project and tracker
208 208 # Returns the moved/copied issue on success, false on failure
209 209 def move_to_project(new_project, new_tracker=nil, options={})
210 210 ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead."
211 211
212 212 if options[:copy]
213 213 issue = self.copy
214 214 else
215 215 issue = self
216 216 end
217 217
218 218 issue.init_journal(User.current, options[:notes])
219 219
220 220 # Preserve previous behaviour
221 221 # #move_to_project doesn't change tracker automatically
222 222 issue.send :project=, new_project, true
223 223 if new_tracker
224 224 issue.tracker = new_tracker
225 225 end
226 226 # Allow bulk setting of attributes on the issue
227 227 if options[:attributes]
228 228 issue.attributes = options[:attributes]
229 229 end
230 230
231 231 issue.save ? issue : false
232 232 end
233 233
234 234 def status_id=(sid)
235 235 self.status = nil
236 236 result = write_attribute(:status_id, sid)
237 237 @workflow_rule_by_attribute = nil
238 238 result
239 239 end
240 240
241 241 def priority_id=(pid)
242 242 self.priority = nil
243 243 write_attribute(:priority_id, pid)
244 244 end
245 245
246 246 def category_id=(cid)
247 247 self.category = nil
248 248 write_attribute(:category_id, cid)
249 249 end
250 250
251 251 def fixed_version_id=(vid)
252 252 self.fixed_version = nil
253 253 write_attribute(:fixed_version_id, vid)
254 254 end
255 255
256 256 def tracker_id=(tid)
257 257 self.tracker = nil
258 258 result = write_attribute(:tracker_id, tid)
259 259 @custom_field_values = nil
260 260 @workflow_rule_by_attribute = nil
261 261 result
262 262 end
263 263
264 264 def project_id=(project_id)
265 265 if project_id.to_s != self.project_id.to_s
266 266 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
267 267 end
268 268 end
269 269
270 270 def project=(project, keep_tracker=false)
271 271 project_was = self.project
272 272 write_attribute(:project_id, project ? project.id : nil)
273 273 association_instance_set('project', project)
274 274 if project_was && project && project_was != project
275 275 @assignable_versions = nil
276 276
277 277 unless keep_tracker || project.trackers.include?(tracker)
278 278 self.tracker = project.trackers.first
279 279 end
280 280 # Reassign to the category with same name if any
281 281 if category
282 282 self.category = project.issue_categories.find_by_name(category.name)
283 283 end
284 284 # Keep the fixed_version if it's still valid in the new_project
285 285 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
286 286 self.fixed_version = nil
287 287 end
288 288 # Clear the parent task if it's no longer valid
289 289 unless valid_parent_project?
290 290 self.parent_issue_id = nil
291 291 end
292 292 @custom_field_values = nil
293 293 end
294 294 end
295 295
296 296 def description=(arg)
297 297 if arg.is_a?(String)
298 298 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
299 299 end
300 300 write_attribute(:description, arg)
301 301 end
302 302
303 303 # Overrides assign_attributes so that project and tracker get assigned first
304 304 def assign_attributes_with_project_and_tracker_first(new_attributes, *args)
305 305 return if new_attributes.nil?
306 306 attrs = new_attributes.dup
307 307 attrs.stringify_keys!
308 308
309 309 %w(project project_id tracker tracker_id).each do |attr|
310 310 if attrs.has_key?(attr)
311 311 send "#{attr}=", attrs.delete(attr)
312 312 end
313 313 end
314 314 send :assign_attributes_without_project_and_tracker_first, attrs, *args
315 315 end
316 316 # Do not redefine alias chain on reload (see #4838)
317 317 alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first)
318 318
319 319 def estimated_hours=(h)
320 320 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
321 321 end
322 322
323 323 safe_attributes 'project_id',
324 324 :if => lambda {|issue, user|
325 325 if issue.new_record?
326 326 issue.copy?
327 327 elsif user.allowed_to?(:move_issues, issue.project)
328 328 projects = Issue.allowed_target_projects_on_move(user)
329 329 projects.include?(issue.project) && projects.size > 1
330 330 end
331 331 }
332 332
333 333 safe_attributes 'tracker_id',
334 334 'status_id',
335 335 'category_id',
336 336 'assigned_to_id',
337 337 'priority_id',
338 338 'fixed_version_id',
339 339 'subject',
340 340 'description',
341 341 'start_date',
342 342 'due_date',
343 343 'done_ratio',
344 344 'estimated_hours',
345 345 'custom_field_values',
346 346 'custom_fields',
347 347 'lock_version',
348 348 'notes',
349 349 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
350 350
351 351 safe_attributes 'status_id',
352 352 'assigned_to_id',
353 353 'fixed_version_id',
354 354 'done_ratio',
355 355 'lock_version',
356 356 'notes',
357 357 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
358 358
359 359 safe_attributes 'notes',
360 360 :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
361 361
362 362 safe_attributes 'private_notes',
363 363 :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
364 364
365 365 safe_attributes 'watcher_user_ids',
366 366 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
367 367
368 368 safe_attributes 'is_private',
369 369 :if => lambda {|issue, user|
370 370 user.allowed_to?(:set_issues_private, issue.project) ||
371 371 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
372 372 }
373 373
374 374 safe_attributes 'parent_issue_id',
375 375 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
376 376 user.allowed_to?(:manage_subtasks, issue.project)}
377 377
378 378 def safe_attribute_names(user=nil)
379 379 names = super
380 380 names -= disabled_core_fields
381 381 names -= read_only_attribute_names(user)
382 382 names
383 383 end
384 384
385 385 # Safely sets attributes
386 386 # Should be called from controllers instead of #attributes=
387 387 # attr_accessible is too rough because we still want things like
388 388 # Issue.new(:project => foo) to work
389 389 def safe_attributes=(attrs, user=User.current)
390 390 return unless attrs.is_a?(Hash)
391 391
392 392 attrs = attrs.dup
393 393
394 394 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
395 395 if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
396 396 if allowed_target_projects(user).collect(&:id).include?(p.to_i)
397 397 self.project_id = p
398 398 end
399 399 end
400 400
401 401 if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
402 402 self.tracker_id = t
403 403 end
404 404
405 405 if (s = attrs.delete('status_id')) && safe_attribute?('status_id')
406 406 if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i)
407 407 self.status_id = s
408 408 end
409 409 end
410 410
411 411 attrs = delete_unsafe_attributes(attrs, user)
412 412 return if attrs.empty?
413 413
414 414 unless leaf?
415 415 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
416 416 end
417 417
418 418 if attrs['parent_issue_id'].present?
419 unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
419 s = attrs['parent_issue_id'].to_s
420 unless (m = s.match(%r{\A#?(\d+)\z})) && Issue.visible(user).exists?(m[1])
420 421 @invalid_parent_issue_id = attrs.delete('parent_issue_id')
421 422 end
422 423 end
423 424
424 425 if attrs['custom_field_values'].present?
425 426 attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| read_only_attribute_names(user).include? k.to_s}
426 427 end
427 428
428 429 if attrs['custom_fields'].present?
429 430 attrs['custom_fields'] = attrs['custom_fields'].reject {|c| read_only_attribute_names(user).include? c['id'].to_s}
430 431 end
431 432
432 433 # mass-assignment security bypass
433 434 assign_attributes attrs, :without_protection => true
434 435 end
435 436
436 437 def disabled_core_fields
437 438 tracker ? tracker.disabled_core_fields : []
438 439 end
439 440
440 441 # Returns the custom_field_values that can be edited by the given user
441 442 def editable_custom_field_values(user=nil)
442 443 custom_field_values.reject do |value|
443 444 read_only_attribute_names(user).include?(value.custom_field_id.to_s)
444 445 end
445 446 end
446 447
447 448 # Returns the names of attributes that are read-only for user or the current user
448 449 # For users with multiple roles, the read-only fields are the intersection of
449 450 # read-only fields of each role
450 451 # The result is an array of strings where sustom fields are represented with their ids
451 452 #
452 453 # Examples:
453 454 # issue.read_only_attribute_names # => ['due_date', '2']
454 455 # issue.read_only_attribute_names(user) # => []
455 456 def read_only_attribute_names(user=nil)
456 457 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys
457 458 end
458 459
459 460 # Returns the names of required attributes for user or the current user
460 461 # For users with multiple roles, the required fields are the intersection of
461 462 # required fields of each role
462 463 # The result is an array of strings where sustom fields are represented with their ids
463 464 #
464 465 # Examples:
465 466 # issue.required_attribute_names # => ['due_date', '2']
466 467 # issue.required_attribute_names(user) # => []
467 468 def required_attribute_names(user=nil)
468 469 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys
469 470 end
470 471
471 472 # Returns true if the attribute is required for user
472 473 def required_attribute?(name, user=nil)
473 474 required_attribute_names(user).include?(name.to_s)
474 475 end
475 476
476 477 # Returns a hash of the workflow rule by attribute for the given user
477 478 #
478 479 # Examples:
479 480 # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'}
480 481 def workflow_rule_by_attribute(user=nil)
481 482 return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil?
482 483
483 484 user_real = user || User.current
484 485 roles = user_real.admin ? Role.all : user_real.roles_for_project(project)
485 486 return {} if roles.empty?
486 487
487 488 result = {}
488 489 workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all
489 490 if workflow_permissions.any?
490 491 workflow_rules = workflow_permissions.inject({}) do |h, wp|
491 492 h[wp.field_name] ||= []
492 493 h[wp.field_name] << wp.rule
493 494 h
494 495 end
495 496 workflow_rules.each do |attr, rules|
496 497 next if rules.size < roles.size
497 498 uniq_rules = rules.uniq
498 499 if uniq_rules.size == 1
499 500 result[attr] = uniq_rules.first
500 501 else
501 502 result[attr] = 'required'
502 503 end
503 504 end
504 505 end
505 506 @workflow_rule_by_attribute = result if user.nil?
506 507 result
507 508 end
508 509 private :workflow_rule_by_attribute
509 510
510 511 def done_ratio
511 512 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
512 513 status.default_done_ratio
513 514 else
514 515 read_attribute(:done_ratio)
515 516 end
516 517 end
517 518
518 519 def self.use_status_for_done_ratio?
519 520 Setting.issue_done_ratio == 'issue_status'
520 521 end
521 522
522 523 def self.use_field_for_done_ratio?
523 524 Setting.issue_done_ratio == 'issue_field'
524 525 end
525 526
526 527 def validate_issue
527 528 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
528 529 errors.add :due_date, :not_a_date
529 530 end
530 531
531 532 if self.due_date and self.start_date and self.due_date < self.start_date
532 533 errors.add :due_date, :greater_than_start_date
533 534 end
534 535
535 536 if start_date && soonest_start && start_date < soonest_start
536 537 errors.add :start_date, :invalid
537 538 end
538 539
539 540 if fixed_version
540 541 if !assignable_versions.include?(fixed_version)
541 542 errors.add :fixed_version_id, :inclusion
542 543 elsif reopened? && fixed_version.closed?
543 544 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
544 545 end
545 546 end
546 547
547 548 # Checks that the issue can not be added/moved to a disabled tracker
548 549 if project && (tracker_id_changed? || project_id_changed?)
549 550 unless project.trackers.include?(tracker)
550 551 errors.add :tracker_id, :inclusion
551 552 end
552 553 end
553 554
554 555 # Checks parent issue assignment
555 556 if @invalid_parent_issue_id.present?
556 557 errors.add :parent_issue_id, :invalid
557 558 elsif @parent_issue
558 559 if !valid_parent_project?(@parent_issue)
559 560 errors.add :parent_issue_id, :invalid
560 561 elsif !new_record?
561 562 # moving an existing issue
562 563 if @parent_issue.root_id != root_id
563 564 # we can always move to another tree
564 565 elsif move_possible?(@parent_issue)
565 566 # move accepted inside tree
566 567 else
567 568 errors.add :parent_issue_id, :invalid
568 569 end
569 570 end
570 571 end
571 572 end
572 573
573 574 # Validates the issue against additional workflow requirements
574 575 def validate_required_fields
575 576 user = new_record? ? author : current_journal.try(:user)
576 577
577 578 required_attribute_names(user).each do |attribute|
578 579 if attribute =~ /^\d+$/
579 580 attribute = attribute.to_i
580 581 v = custom_field_values.detect {|v| v.custom_field_id == attribute }
581 582 if v && v.value.blank?
582 583 errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank')
583 584 end
584 585 else
585 586 if respond_to?(attribute) && send(attribute).blank?
586 587 errors.add attribute, :blank
587 588 end
588 589 end
589 590 end
590 591 end
591 592
592 593 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
593 594 # even if the user turns off the setting later
594 595 def update_done_ratio_from_issue_status
595 596 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
596 597 self.done_ratio = status.default_done_ratio
597 598 end
598 599 end
599 600
600 601 def init_journal(user, notes = "")
601 602 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
602 603 if new_record?
603 604 @current_journal.notify = false
604 605 else
605 606 @attributes_before_change = attributes.dup
606 607 @custom_values_before_change = {}
607 608 self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
608 609 end
609 610 @current_journal
610 611 end
611 612
612 613 # Returns the id of the last journal or nil
613 614 def last_journal_id
614 615 if new_record?
615 616 nil
616 617 else
617 618 journals.maximum(:id)
618 619 end
619 620 end
620 621
621 622 # Returns a scope for journals that have an id greater than journal_id
622 623 def journals_after(journal_id)
623 624 scope = journals.reorder("#{Journal.table_name}.id ASC")
624 625 if journal_id.present?
625 626 scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i)
626 627 end
627 628 scope
628 629 end
629 630
630 631 # Return true if the issue is closed, otherwise false
631 632 def closed?
632 633 self.status.is_closed?
633 634 end
634 635
635 636 # Return true if the issue is being reopened
636 637 def reopened?
637 638 if !new_record? && status_id_changed?
638 639 status_was = IssueStatus.find_by_id(status_id_was)
639 640 status_new = IssueStatus.find_by_id(status_id)
640 641 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
641 642 return true
642 643 end
643 644 end
644 645 false
645 646 end
646 647
647 648 # Return true if the issue is being closed
648 649 def closing?
649 650 if !new_record? && status_id_changed?
650 651 status_was = IssueStatus.find_by_id(status_id_was)
651 652 status_new = IssueStatus.find_by_id(status_id)
652 653 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
653 654 return true
654 655 end
655 656 end
656 657 false
657 658 end
658 659
659 660 # Returns true if the issue is overdue
660 661 def overdue?
661 662 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
662 663 end
663 664
664 665 # Is the amount of work done less than it should for the due date
665 666 def behind_schedule?
666 667 return false if start_date.nil? || due_date.nil?
667 668 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
668 669 return done_date <= Date.today
669 670 end
670 671
671 672 # Does this issue have children?
672 673 def children?
673 674 !leaf?
674 675 end
675 676
676 677 # Users the issue can be assigned to
677 678 def assignable_users
678 679 users = project.assignable_users
679 680 users << author if author
680 681 users << assigned_to if assigned_to
681 682 users.uniq.sort
682 683 end
683 684
684 685 # Versions that the issue can be assigned to
685 686 def assignable_versions
686 687 return @assignable_versions if @assignable_versions
687 688
688 689 versions = project.shared_versions.open.all
689 690 if fixed_version
690 691 if fixed_version_id_changed?
691 692 # nothing to do
692 693 elsif project_id_changed?
693 694 if project.shared_versions.include?(fixed_version)
694 695 versions << fixed_version
695 696 end
696 697 else
697 698 versions << fixed_version
698 699 end
699 700 end
700 701 @assignable_versions = versions.uniq.sort
701 702 end
702 703
703 704 # Returns true if this issue is blocked by another issue that is still open
704 705 def blocked?
705 706 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
706 707 end
707 708
708 709 # Returns an array of statuses that user is able to apply
709 710 def new_statuses_allowed_to(user=User.current, include_default=false)
710 711 if new_record? && @copied_from
711 712 [IssueStatus.default, @copied_from.status].compact.uniq.sort
712 713 else
713 714 initial_status = nil
714 715 if new_record?
715 716 initial_status = IssueStatus.default
716 717 elsif status_id_was
717 718 initial_status = IssueStatus.find_by_id(status_id_was)
718 719 end
719 720 initial_status ||= status
720 721
721 722 statuses = initial_status.find_new_statuses_allowed_to(
722 723 user.admin ? Role.all : user.roles_for_project(project),
723 724 tracker,
724 725 author == user,
725 726 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
726 727 )
727 728 statuses << initial_status unless statuses.empty?
728 729 statuses << IssueStatus.default if include_default
729 730 statuses = statuses.compact.uniq.sort
730 731 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
731 732 end
732 733 end
733 734
734 735 def assigned_to_was
735 736 if assigned_to_id_changed? && assigned_to_id_was.present?
736 737 @assigned_to_was ||= User.find_by_id(assigned_to_id_was)
737 738 end
738 739 end
739 740
740 741 # Returns the users that should be notified
741 742 def notified_users
742 743 notified = []
743 744 # Author and assignee are always notified unless they have been
744 745 # locked or don't want to be notified
745 746 notified << author if author
746 747 if assigned_to
747 748 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
748 749 end
749 750 if assigned_to_was
750 751 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
751 752 end
752 753 notified = notified.select {|u| u.active? && u.notify_about?(self)}
753 754
754 755 notified += project.notified_users
755 756 notified.uniq!
756 757 # Remove users that can not view the issue
757 758 notified.reject! {|user| !visible?(user)}
758 759 notified
759 760 end
760 761
761 762 # Returns the email addresses that should be notified
762 763 def recipients
763 764 notified_users.collect(&:mail)
764 765 end
765 766
766 767 # Returns the number of hours spent on this issue
767 768 def spent_hours
768 769 @spent_hours ||= time_entries.sum(:hours) || 0
769 770 end
770 771
771 772 # Returns the total number of hours spent on this issue and its descendants
772 773 #
773 774 # Example:
774 775 # spent_hours => 0.0
775 776 # spent_hours => 50.2
776 777 def total_spent_hours
777 778 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours",
778 779 :joins => "LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").to_f || 0.0
779 780 end
780 781
781 782 def relations
782 783 @relations ||= IssueRelations.new(self, (relations_from + relations_to).sort)
783 784 end
784 785
785 786 # Preloads relations for a collection of issues
786 787 def self.load_relations(issues)
787 788 if issues.any?
788 789 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
789 790 issues.each do |issue|
790 791 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
791 792 end
792 793 end
793 794 end
794 795
795 796 # Preloads visible spent time for a collection of issues
796 797 def self.load_visible_spent_hours(issues, user=User.current)
797 798 if issues.any?
798 799 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
799 800 issues.each do |issue|
800 801 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
801 802 end
802 803 end
803 804 end
804 805
805 806 # Preloads visible relations for a collection of issues
806 807 def self.load_visible_relations(issues, user=User.current)
807 808 if issues.any?
808 809 issue_ids = issues.map(&:id)
809 810 # Relations with issue_from in given issues and visible issue_to
810 811 relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all
811 812 # Relations with issue_to in given issues and visible issue_from
812 813 relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all
813 814
814 815 issues.each do |issue|
815 816 relations =
816 817 relations_from.select {|relation| relation.issue_from_id == issue.id} +
817 818 relations_to.select {|relation| relation.issue_to_id == issue.id}
818 819
819 820 issue.instance_variable_set "@relations", IssueRelations.new(issue, relations.sort)
820 821 end
821 822 end
822 823 end
823 824
824 825 # Finds an issue relation given its id.
825 826 def find_relation(relation_id)
826 827 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
827 828 end
828 829
829 830 def all_dependent_issues(except=[])
830 831 except << self
831 832 dependencies = []
832 833 relations_from.each do |relation|
833 834 if relation.issue_to && !except.include?(relation.issue_to)
834 835 dependencies << relation.issue_to
835 836 dependencies += relation.issue_to.all_dependent_issues(except)
836 837 end
837 838 end
838 839 dependencies
839 840 end
840 841
841 842 # Returns an array of issues that duplicate this one
842 843 def duplicates
843 844 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
844 845 end
845 846
846 847 # Returns the due date or the target due date if any
847 848 # Used on gantt chart
848 849 def due_before
849 850 due_date || (fixed_version ? fixed_version.effective_date : nil)
850 851 end
851 852
852 853 # Returns the time scheduled for this issue.
853 854 #
854 855 # Example:
855 856 # Start Date: 2/26/09, End Date: 3/04/09
856 857 # duration => 6
857 858 def duration
858 859 (start_date && due_date) ? due_date - start_date : 0
859 860 end
860 861
861 862 def soonest_start
862 863 @soonest_start ||= (
863 864 relations_to.collect{|relation| relation.successor_soonest_start} +
864 865 ancestors.collect(&:soonest_start)
865 866 ).compact.max
866 867 end
867 868
868 869 def reschedule_after(date)
869 870 return if date.nil?
870 871 if leaf?
871 872 if start_date.nil? || start_date < date
872 873 self.start_date, self.due_date = date, date + duration
873 874 begin
874 875 save
875 876 rescue ActiveRecord::StaleObjectError
876 877 reload
877 878 self.start_date, self.due_date = date, date + duration
878 879 save
879 880 end
880 881 end
881 882 else
882 883 leaves.each do |leaf|
883 884 leaf.reschedule_after(date)
884 885 end
885 886 end
886 887 end
887 888
888 889 def <=>(issue)
889 890 if issue.nil?
890 891 -1
891 892 elsif root_id != issue.root_id
892 893 (root_id || 0) <=> (issue.root_id || 0)
893 894 else
894 895 (lft || 0) <=> (issue.lft || 0)
895 896 end
896 897 end
897 898
898 899 def to_s
899 900 "#{tracker} ##{id}: #{subject}"
900 901 end
901 902
902 903 # Returns a string of css classes that apply to the issue
903 904 def css_classes
904 905 s = "issue status-#{status_id} priority-#{priority_id}"
905 906 s << ' closed' if closed?
906 907 s << ' overdue' if overdue?
907 908 s << ' child' if child?
908 909 s << ' parent' unless leaf?
909 910 s << ' private' if is_private?
910 911 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
911 912 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
912 913 s
913 914 end
914 915
915 916 # Saves an issue and a time_entry from the parameters
916 917 def save_issue_with_child_records(params, existing_time_entry=nil)
917 918 Issue.transaction do
918 919 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
919 920 @time_entry = existing_time_entry || TimeEntry.new
920 921 @time_entry.project = project
921 922 @time_entry.issue = self
922 923 @time_entry.user = User.current
923 924 @time_entry.spent_on = User.current.today
924 925 @time_entry.attributes = params[:time_entry]
925 926 self.time_entries << @time_entry
926 927 end
927 928
928 929 # TODO: Rename hook
929 930 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
930 931 if save
931 932 # TODO: Rename hook
932 933 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
933 934 else
934 935 raise ActiveRecord::Rollback
935 936 end
936 937 end
937 938 end
938 939
939 940 # Unassigns issues from +version+ if it's no longer shared with issue's project
940 941 def self.update_versions_from_sharing_change(version)
941 942 # Update issues assigned to the version
942 943 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
943 944 end
944 945
945 946 # Unassigns issues from versions that are no longer shared
946 947 # after +project+ was moved
947 948 def self.update_versions_from_hierarchy_change(project)
948 949 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
949 950 # Update issues of the moved projects and issues assigned to a version of a moved project
950 951 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
951 952 end
952 953
953 954 def parent_issue_id=(arg)
954 955 s = arg.to_s.strip.presence
955 956 if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
956 957 @parent_issue.id
957 958 else
958 959 @parent_issue = nil
959 960 @invalid_parent_issue_id = arg
960 961 end
961 962 end
962 963
963 964 def parent_issue_id
964 965 if @invalid_parent_issue_id
965 966 @invalid_parent_issue_id
966 967 elsif instance_variable_defined? :@parent_issue
967 968 @parent_issue.nil? ? nil : @parent_issue.id
968 969 else
969 970 parent_id
970 971 end
971 972 end
972 973
973 974 # Returns true if issue's project is a valid
974 975 # parent issue project
975 976 def valid_parent_project?(issue=parent)
976 977 return true if issue.nil? || issue.project_id == project_id
977 978
978 979 case Setting.cross_project_subtasks
979 980 when 'system'
980 981 true
981 982 when 'tree'
982 983 issue.project.root == project.root
983 984 when 'hierarchy'
984 985 issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
985 986 when 'descendants'
986 987 issue.project.is_or_is_ancestor_of?(project)
987 988 else
988 989 false
989 990 end
990 991 end
991 992
992 993 # Extracted from the ReportsController.
993 994 def self.by_tracker(project)
994 995 count_and_group_by(:project => project,
995 996 :field => 'tracker_id',
996 997 :joins => Tracker.table_name)
997 998 end
998 999
999 1000 def self.by_version(project)
1000 1001 count_and_group_by(:project => project,
1001 1002 :field => 'fixed_version_id',
1002 1003 :joins => Version.table_name)
1003 1004 end
1004 1005
1005 1006 def self.by_priority(project)
1006 1007 count_and_group_by(:project => project,
1007 1008 :field => 'priority_id',
1008 1009 :joins => IssuePriority.table_name)
1009 1010 end
1010 1011
1011 1012 def self.by_category(project)
1012 1013 count_and_group_by(:project => project,
1013 1014 :field => 'category_id',
1014 1015 :joins => IssueCategory.table_name)
1015 1016 end
1016 1017
1017 1018 def self.by_assigned_to(project)
1018 1019 count_and_group_by(:project => project,
1019 1020 :field => 'assigned_to_id',
1020 1021 :joins => User.table_name)
1021 1022 end
1022 1023
1023 1024 def self.by_author(project)
1024 1025 count_and_group_by(:project => project,
1025 1026 :field => 'author_id',
1026 1027 :joins => User.table_name)
1027 1028 end
1028 1029
1029 1030 def self.by_subproject(project)
1030 1031 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1031 1032 s.is_closed as closed,
1032 1033 #{Issue.table_name}.project_id as project_id,
1033 1034 count(#{Issue.table_name}.id) as total
1034 1035 from
1035 1036 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
1036 1037 where
1037 1038 #{Issue.table_name}.status_id=s.id
1038 1039 and #{Issue.table_name}.project_id = #{Project.table_name}.id
1039 1040 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
1040 1041 and #{Issue.table_name}.project_id <> #{project.id}
1041 1042 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
1042 1043 end
1043 1044 # End ReportsController extraction
1044 1045
1045 1046 # Returns an array of projects that user can assign the issue to
1046 1047 def allowed_target_projects(user=User.current)
1047 1048 if new_record?
1048 1049 Project.all(:conditions => Project.allowed_to_condition(user, :add_issues))
1049 1050 else
1050 1051 self.class.allowed_target_projects_on_move(user)
1051 1052 end
1052 1053 end
1053 1054
1054 1055 # Returns an array of projects that user can move issues to
1055 1056 def self.allowed_target_projects_on_move(user=User.current)
1056 1057 Project.all(:conditions => Project.allowed_to_condition(user, :move_issues))
1057 1058 end
1058 1059
1059 1060 private
1060 1061
1061 1062 def after_project_change
1062 1063 # Update project_id on related time entries
1063 1064 TimeEntry.update_all(["project_id = ?", project_id], {:issue_id => id})
1064 1065
1065 1066 # Delete issue relations
1066 1067 unless Setting.cross_project_issue_relations?
1067 1068 relations_from.clear
1068 1069 relations_to.clear
1069 1070 end
1070 1071
1071 1072 # Move subtasks that were in the same project
1072 1073 children.each do |child|
1073 1074 next unless child.project_id == project_id_was
1074 1075 # Change project and keep project
1075 1076 child.send :project=, project, true
1076 1077 unless child.save
1077 1078 raise ActiveRecord::Rollback
1078 1079 end
1079 1080 end
1080 1081 end
1081 1082
1082 1083 # Callback for after the creation of an issue by copy
1083 1084 # * adds a "copied to" relation with the copied issue
1084 1085 # * copies subtasks from the copied issue
1085 1086 def after_create_from_copy
1086 1087 return unless copy? && !@after_create_from_copy_handled
1087 1088
1088 1089 if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
1089 1090 relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
1090 1091 unless relation.save
1091 1092 logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
1092 1093 end
1093 1094 end
1094 1095
1095 1096 unless @copied_from.leaf? || @copy_options[:subtasks] == false
1096 1097 @copied_from.children.each do |child|
1097 1098 unless child.visible?
1098 1099 # Do not copy subtasks that are not visible to avoid potential disclosure of private data
1099 1100 logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger
1100 1101 next
1101 1102 end
1102 1103 copy = Issue.new.copy_from(child, @copy_options)
1103 1104 copy.author = author
1104 1105 copy.project = project
1105 1106 copy.parent_issue_id = id
1106 1107 # Children subtasks are copied recursively
1107 1108 unless copy.save
1108 1109 logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
1109 1110 end
1110 1111 end
1111 1112 end
1112 1113 @after_create_from_copy_handled = true
1113 1114 end
1114 1115
1115 1116 def update_nested_set_attributes
1116 1117 if root_id.nil?
1117 1118 # issue was just created
1118 1119 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
1119 1120 set_default_left_and_right
1120 1121 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
1121 1122 if @parent_issue
1122 1123 move_to_child_of(@parent_issue)
1123 1124 end
1124 1125 reload
1125 1126 elsif parent_issue_id != parent_id
1126 1127 former_parent_id = parent_id
1127 1128 # moving an existing issue
1128 1129 if @parent_issue && @parent_issue.root_id == root_id
1129 1130 # inside the same tree
1130 1131 move_to_child_of(@parent_issue)
1131 1132 else
1132 1133 # to another tree
1133 1134 unless root?
1134 1135 move_to_right_of(root)
1135 1136 reload
1136 1137 end
1137 1138 old_root_id = root_id
1138 1139 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
1139 1140 target_maxright = nested_set_scope.maximum(right_column_name) || 0
1140 1141 offset = target_maxright + 1 - lft
1141 1142 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
1142 1143 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
1143 1144 self[left_column_name] = lft + offset
1144 1145 self[right_column_name] = rgt + offset
1145 1146 if @parent_issue
1146 1147 move_to_child_of(@parent_issue)
1147 1148 end
1148 1149 end
1149 1150 reload
1150 1151 # delete invalid relations of all descendants
1151 1152 self_and_descendants.each do |issue|
1152 1153 issue.relations.each do |relation|
1153 1154 relation.destroy unless relation.valid?
1154 1155 end
1155 1156 end
1156 1157 # update former parent
1157 1158 recalculate_attributes_for(former_parent_id) if former_parent_id
1158 1159 end
1159 1160 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
1160 1161 end
1161 1162
1162 1163 def update_parent_attributes
1163 1164 recalculate_attributes_for(parent_id) if parent_id
1164 1165 end
1165 1166
1166 1167 def recalculate_attributes_for(issue_id)
1167 1168 if issue_id && p = Issue.find_by_id(issue_id)
1168 1169 # priority = highest priority of children
1169 1170 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :joins => :priority)
1170 1171 p.priority = IssuePriority.find_by_position(priority_position)
1171 1172 end
1172 1173
1173 1174 # start/due dates = lowest/highest dates of children
1174 1175 p.start_date = p.children.minimum(:start_date)
1175 1176 p.due_date = p.children.maximum(:due_date)
1176 1177 if p.start_date && p.due_date && p.due_date < p.start_date
1177 1178 p.start_date, p.due_date = p.due_date, p.start_date
1178 1179 end
1179 1180
1180 1181 # done ratio = weighted average ratio of leaves
1181 1182 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
1182 1183 leaves_count = p.leaves.count
1183 1184 if leaves_count > 0
1184 1185 average = p.leaves.average(:estimated_hours).to_f
1185 1186 if average == 0
1186 1187 average = 1
1187 1188 end
1188 1189 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :joins => :status).to_f
1189 1190 progress = done / (average * leaves_count)
1190 1191 p.done_ratio = progress.round
1191 1192 end
1192 1193 end
1193 1194
1194 1195 # estimate = sum of leaves estimates
1195 1196 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
1196 1197 p.estimated_hours = nil if p.estimated_hours == 0.0
1197 1198
1198 1199 # ancestors will be recursively updated
1199 1200 p.save(:validate => false)
1200 1201 end
1201 1202 end
1202 1203
1203 1204 # Update issues so their versions are not pointing to a
1204 1205 # fixed_version that is not shared with the issue's project
1205 1206 def self.update_versions(conditions=nil)
1206 1207 # Only need to update issues with a fixed_version from
1207 1208 # a different project and that is not systemwide shared
1208 1209 Issue.scoped(:conditions => conditions).all(
1209 1210 :conditions => "#{Issue.table_name}.fixed_version_id IS NOT NULL" +
1210 1211 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
1211 1212 " AND #{Version.table_name}.sharing <> 'system'",
1212 1213 :include => [:project, :fixed_version]
1213 1214 ).each do |issue|
1214 1215 next if issue.project.nil? || issue.fixed_version.nil?
1215 1216 unless issue.project.shared_versions.include?(issue.fixed_version)
1216 1217 issue.init_journal(User.current)
1217 1218 issue.fixed_version = nil
1218 1219 issue.save
1219 1220 end
1220 1221 end
1221 1222 end
1222 1223
1223 1224 # Callback on file attachment
1224 1225 def attachment_added(obj)
1225 1226 if @current_journal && !obj.new_record?
1226 1227 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
1227 1228 end
1228 1229 end
1229 1230
1230 1231 # Callback on attachment deletion
1231 1232 def attachment_removed(obj)
1232 1233 if @current_journal && !obj.new_record?
1233 1234 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename)
1234 1235 @current_journal.save
1235 1236 end
1236 1237 end
1237 1238
1238 1239 # Default assignment based on category
1239 1240 def default_assign
1240 1241 if assigned_to.nil? && category && category.assigned_to
1241 1242 self.assigned_to = category.assigned_to
1242 1243 end
1243 1244 end
1244 1245
1245 1246 # Updates start/due dates of following issues
1246 1247 def reschedule_following_issues
1247 1248 if start_date_changed? || due_date_changed?
1248 1249 relations_from.each do |relation|
1249 1250 relation.set_issue_to_dates
1250 1251 end
1251 1252 end
1252 1253 end
1253 1254
1254 1255 # Closes duplicates if the issue is being closed
1255 1256 def close_duplicates
1256 1257 if closing?
1257 1258 duplicates.each do |duplicate|
1258 1259 # Reload is need in case the duplicate was updated by a previous duplicate
1259 1260 duplicate.reload
1260 1261 # Don't re-close it if it's already closed
1261 1262 next if duplicate.closed?
1262 1263 # Same user and notes
1263 1264 if @current_journal
1264 1265 duplicate.init_journal(@current_journal.user, @current_journal.notes)
1265 1266 end
1266 1267 duplicate.update_attribute :status, self.status
1267 1268 end
1268 1269 end
1269 1270 end
1270 1271
1271 1272 # Make sure updated_on is updated when adding a note
1272 1273 def force_updated_on_change
1273 1274 if @current_journal
1274 1275 self.updated_on = current_time_from_proper_timezone
1275 1276 end
1276 1277 end
1277 1278
1278 1279 # Saves the changes in a Journal
1279 1280 # Called after_save
1280 1281 def create_journal
1281 1282 if @current_journal
1282 1283 # attributes changes
1283 1284 if @attributes_before_change
1284 1285 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
1285 1286 before = @attributes_before_change[c]
1286 1287 after = send(c)
1287 1288 next if before == after || (before.blank? && after.blank?)
1288 1289 @current_journal.details << JournalDetail.new(:property => 'attr',
1289 1290 :prop_key => c,
1290 1291 :old_value => before,
1291 1292 :value => after)
1292 1293 }
1293 1294 end
1294 1295 if @custom_values_before_change
1295 1296 # custom fields changes
1296 1297 custom_field_values.each {|c|
1297 1298 before = @custom_values_before_change[c.custom_field_id]
1298 1299 after = c.value
1299 1300 next if before == after || (before.blank? && after.blank?)
1300 1301
1301 1302 if before.is_a?(Array) || after.is_a?(Array)
1302 1303 before = [before] unless before.is_a?(Array)
1303 1304 after = [after] unless after.is_a?(Array)
1304 1305
1305 1306 # values removed
1306 1307 (before - after).reject(&:blank?).each do |value|
1307 1308 @current_journal.details << JournalDetail.new(:property => 'cf',
1308 1309 :prop_key => c.custom_field_id,
1309 1310 :old_value => value,
1310 1311 :value => nil)
1311 1312 end
1312 1313 # values added
1313 1314 (after - before).reject(&:blank?).each do |value|
1314 1315 @current_journal.details << JournalDetail.new(:property => 'cf',
1315 1316 :prop_key => c.custom_field_id,
1316 1317 :old_value => nil,
1317 1318 :value => value)
1318 1319 end
1319 1320 else
1320 1321 @current_journal.details << JournalDetail.new(:property => 'cf',
1321 1322 :prop_key => c.custom_field_id,
1322 1323 :old_value => before,
1323 1324 :value => after)
1324 1325 end
1325 1326 }
1326 1327 end
1327 1328 @current_journal.save
1328 1329 # reset current journal
1329 1330 init_journal @current_journal.user, @current_journal.notes
1330 1331 end
1331 1332 end
1332 1333
1333 1334 # Query generator for selecting groups of issue counts for a project
1334 1335 # based on specific criteria
1335 1336 #
1336 1337 # Options
1337 1338 # * project - Project to search in.
1338 1339 # * field - String. Issue field to key off of in the grouping.
1339 1340 # * joins - String. The table name to join against.
1340 1341 def self.count_and_group_by(options)
1341 1342 project = options.delete(:project)
1342 1343 select_field = options.delete(:field)
1343 1344 joins = options.delete(:joins)
1344 1345
1345 1346 where = "#{Issue.table_name}.#{select_field}=j.id"
1346 1347
1347 1348 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1348 1349 s.is_closed as closed,
1349 1350 j.id as #{select_field},
1350 1351 count(#{Issue.table_name}.id) as total
1351 1352 from
1352 1353 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
1353 1354 where
1354 1355 #{Issue.table_name}.status_id=s.id
1355 1356 and #{where}
1356 1357 and #{Issue.table_name}.project_id=#{Project.table_name}.id
1357 1358 and #{visible_condition(User.current, :project => project)}
1358 1359 group by s.id, s.is_closed, j.id")
1359 1360 end
1360 1361 end
@@ -1,3793 +1,3805
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2012 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 :repositories,
46 46 :changesets
47 47
48 48 include Redmine::I18n
49 49
50 50 def setup
51 51 @controller = IssuesController.new
52 52 @request = ActionController::TestRequest.new
53 53 @response = ActionController::TestResponse.new
54 54 User.current = nil
55 55 end
56 56
57 57 def test_index
58 58 with_settings :default_language => "en" do
59 59 get :index
60 60 assert_response :success
61 61 assert_template 'index'
62 62 assert_not_nil assigns(:issues)
63 63 assert_nil assigns(:project)
64 64
65 65 # links to visible issues
66 66 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
67 67 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
68 68 # private projects hidden
69 69 assert_select 'a[href=/issues/6]', 0
70 70 assert_select 'a[href=/issues/4]', 0
71 71 # project column
72 72 assert_select 'th', :text => /Project/
73 73 end
74 74 end
75 75
76 76 def test_index_should_not_list_issues_when_module_disabled
77 77 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
78 78 get :index
79 79 assert_response :success
80 80 assert_template 'index'
81 81 assert_not_nil assigns(:issues)
82 82 assert_nil assigns(:project)
83 83
84 84 assert_select 'a[href=/issues/1]', 0
85 85 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
86 86 end
87 87
88 88 def test_index_should_list_visible_issues_only
89 89 get :index, :per_page => 100
90 90 assert_response :success
91 91 assert_not_nil assigns(:issues)
92 92 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
93 93 end
94 94
95 95 def test_index_with_project
96 96 Setting.display_subprojects_issues = 0
97 97 get :index, :project_id => 1
98 98 assert_response :success
99 99 assert_template 'index'
100 100 assert_not_nil assigns(:issues)
101 101
102 102 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
103 103 assert_select 'a[href=/issues/5]', 0
104 104 end
105 105
106 106 def test_index_with_project_and_subprojects
107 107 Setting.display_subprojects_issues = 1
108 108 get :index, :project_id => 1
109 109 assert_response :success
110 110 assert_template 'index'
111 111 assert_not_nil assigns(:issues)
112 112
113 113 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
114 114 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
115 115 assert_select 'a[href=/issues/6]', 0
116 116 end
117 117
118 118 def test_index_with_project_and_subprojects_should_show_private_subprojects_with_permission
119 119 @request.session[:user_id] = 2
120 120 Setting.display_subprojects_issues = 1
121 121 get :index, :project_id => 1
122 122 assert_response :success
123 123 assert_template 'index'
124 124 assert_not_nil assigns(:issues)
125 125
126 126 assert_select 'a[href=/issues/1]', :text => /Can&#x27;t print recipes/
127 127 assert_select 'a[href=/issues/5]', :text => /Subproject issue/
128 128 assert_select 'a[href=/issues/6]', :text => /Issue of a private subproject/
129 129 end
130 130
131 131 def test_index_with_project_and_default_filter
132 132 get :index, :project_id => 1, :set_filter => 1
133 133 assert_response :success
134 134 assert_template 'index'
135 135 assert_not_nil assigns(:issues)
136 136
137 137 query = assigns(:query)
138 138 assert_not_nil query
139 139 # default filter
140 140 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
141 141 end
142 142
143 143 def test_index_with_project_and_filter
144 144 get :index, :project_id => 1, :set_filter => 1,
145 145 :f => ['tracker_id'],
146 146 :op => {'tracker_id' => '='},
147 147 :v => {'tracker_id' => ['1']}
148 148 assert_response :success
149 149 assert_template 'index'
150 150 assert_not_nil assigns(:issues)
151 151
152 152 query = assigns(:query)
153 153 assert_not_nil query
154 154 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
155 155 end
156 156
157 157 def test_index_with_short_filters
158 158 to_test = {
159 159 'status_id' => {
160 160 'o' => { :op => 'o', :values => [''] },
161 161 'c' => { :op => 'c', :values => [''] },
162 162 '7' => { :op => '=', :values => ['7'] },
163 163 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
164 164 '=7' => { :op => '=', :values => ['7'] },
165 165 '!3' => { :op => '!', :values => ['3'] },
166 166 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
167 167 'subject' => {
168 168 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
169 169 'o' => { :op => '=', :values => ['o'] },
170 170 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
171 171 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
172 172 'tracker_id' => {
173 173 '3' => { :op => '=', :values => ['3'] },
174 174 '=3' => { :op => '=', :values => ['3'] }},
175 175 'start_date' => {
176 176 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
177 177 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
178 178 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
179 179 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
180 180 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
181 181 '<t+2' => { :op => '<t+', :values => ['2'] },
182 182 '>t+2' => { :op => '>t+', :values => ['2'] },
183 183 't+2' => { :op => 't+', :values => ['2'] },
184 184 't' => { :op => 't', :values => [''] },
185 185 'w' => { :op => 'w', :values => [''] },
186 186 '>t-2' => { :op => '>t-', :values => ['2'] },
187 187 '<t-2' => { :op => '<t-', :values => ['2'] },
188 188 't-2' => { :op => 't-', :values => ['2'] }},
189 189 'created_on' => {
190 190 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
191 191 '<t-2' => { :op => '<t-', :values => ['2'] },
192 192 '>t-2' => { :op => '>t-', :values => ['2'] },
193 193 't-2' => { :op => 't-', :values => ['2'] }},
194 194 'cf_1' => {
195 195 'c' => { :op => '=', :values => ['c'] },
196 196 '!c' => { :op => '!', :values => ['c'] },
197 197 '!*' => { :op => '!*', :values => [''] },
198 198 '*' => { :op => '*', :values => [''] }},
199 199 'estimated_hours' => {
200 200 '=13.4' => { :op => '=', :values => ['13.4'] },
201 201 '>=45' => { :op => '>=', :values => ['45'] },
202 202 '<=125' => { :op => '<=', :values => ['125'] },
203 203 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
204 204 '!*' => { :op => '!*', :values => [''] },
205 205 '*' => { :op => '*', :values => [''] }}
206 206 }
207 207
208 208 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
209 209
210 210 to_test.each do |field, expression_and_expected|
211 211 expression_and_expected.each do |filter_expression, expected|
212 212
213 213 get :index, :set_filter => 1, field => filter_expression
214 214
215 215 assert_response :success
216 216 assert_template 'index'
217 217 assert_not_nil assigns(:issues)
218 218
219 219 query = assigns(:query)
220 220 assert_not_nil query
221 221 assert query.has_filter?(field)
222 222 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
223 223 end
224 224 end
225 225 end
226 226
227 227 def test_index_with_project_and_empty_filters
228 228 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
229 229 assert_response :success
230 230 assert_template 'index'
231 231 assert_not_nil assigns(:issues)
232 232
233 233 query = assigns(:query)
234 234 assert_not_nil query
235 235 # no filter
236 236 assert_equal({}, query.filters)
237 237 end
238 238
239 239 def test_index_with_project_custom_field_filter
240 240 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
241 241 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
242 242 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
243 243 filter_name = "project.cf_#{field.id}"
244 244 @request.session[:user_id] = 1
245 245
246 246 get :index, :set_filter => 1,
247 247 :f => [filter_name],
248 248 :op => {filter_name => '='},
249 249 :v => {filter_name => ['Foo']}
250 250 assert_response :success
251 251 assert_template 'index'
252 252 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
253 253 end
254 254
255 255 def test_index_with_query
256 256 get :index, :project_id => 1, :query_id => 5
257 257 assert_response :success
258 258 assert_template 'index'
259 259 assert_not_nil assigns(:issues)
260 260 assert_nil assigns(:issue_count_by_group)
261 261 end
262 262
263 263 def test_index_with_query_grouped_by_tracker
264 264 get :index, :project_id => 1, :query_id => 6
265 265 assert_response :success
266 266 assert_template 'index'
267 267 assert_not_nil assigns(:issues)
268 268 assert_not_nil assigns(:issue_count_by_group)
269 269 end
270 270
271 271 def test_index_with_query_grouped_by_list_custom_field
272 272 get :index, :project_id => 1, :query_id => 9
273 273 assert_response :success
274 274 assert_template 'index'
275 275 assert_not_nil assigns(:issues)
276 276 assert_not_nil assigns(:issue_count_by_group)
277 277 end
278 278
279 279 def test_index_with_query_grouped_by_user_custom_field
280 280 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
281 281 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
282 282 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
283 283 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
284 284 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
285 285
286 286 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
287 287 assert_response :success
288 288
289 289 assert_select 'tr.group', 3
290 290 assert_select 'tr.group' do
291 291 assert_select 'a', :text => 'John Smith'
292 292 assert_select 'span.count', :text => '1'
293 293 end
294 294 assert_select 'tr.group' do
295 295 assert_select 'a', :text => 'Dave Lopper'
296 296 assert_select 'span.count', :text => '2'
297 297 end
298 298 end
299 299
300 300 def test_index_with_query_id_and_project_id_should_set_session_query
301 301 get :index, :project_id => 1, :query_id => 4
302 302 assert_response :success
303 303 assert_kind_of Hash, session[:query]
304 304 assert_equal 4, session[:query][:id]
305 305 assert_equal 1, session[:query][:project_id]
306 306 end
307 307
308 308 def test_index_with_invalid_query_id_should_respond_404
309 309 get :index, :project_id => 1, :query_id => 999
310 310 assert_response 404
311 311 end
312 312
313 313 def test_index_with_cross_project_query_in_session_should_show_project_issues
314 314 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
315 315 @request.session[:query] = {:id => q.id, :project_id => 1}
316 316
317 317 with_settings :display_subprojects_issues => '0' do
318 318 get :index, :project_id => 1
319 319 end
320 320 assert_response :success
321 321 assert_not_nil assigns(:query)
322 322 assert_equal q.id, assigns(:query).id
323 323 assert_equal 1, assigns(:query).project_id
324 324 assert_equal [1], assigns(:issues).map(&:project_id).uniq
325 325 end
326 326
327 327 def test_private_query_should_not_be_available_to_other_users
328 328 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
329 329 @request.session[:user_id] = 3
330 330
331 331 get :index, :query_id => q.id
332 332 assert_response 403
333 333 end
334 334
335 335 def test_private_query_should_be_available_to_its_user
336 336 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
337 337 @request.session[:user_id] = 2
338 338
339 339 get :index, :query_id => q.id
340 340 assert_response :success
341 341 end
342 342
343 343 def test_public_query_should_be_available_to_other_users
344 344 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
345 345 @request.session[:user_id] = 3
346 346
347 347 get :index, :query_id => q.id
348 348 assert_response :success
349 349 end
350 350
351 351 def test_index_should_omit_page_param_in_export_links
352 352 get :index, :page => 2
353 353 assert_response :success
354 354 assert_select 'a.atom[href=/issues.atom]'
355 355 assert_select 'a.csv[href=/issues.csv]'
356 356 assert_select 'a.pdf[href=/issues.pdf]'
357 357 assert_select 'form#csv-export-form[action=/issues.csv]'
358 358 end
359 359
360 360 def test_index_csv
361 361 get :index, :format => 'csv'
362 362 assert_response :success
363 363 assert_not_nil assigns(:issues)
364 364 assert_equal 'text/csv; header=present', @response.content_type
365 365 assert @response.body.starts_with?("#,")
366 366 lines = @response.body.chomp.split("\n")
367 367 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
368 368 end
369 369
370 370 def test_index_csv_with_project
371 371 get :index, :project_id => 1, :format => 'csv'
372 372 assert_response :success
373 373 assert_not_nil assigns(:issues)
374 374 assert_equal 'text/csv; header=present', @response.content_type
375 375 end
376 376
377 377 def test_index_csv_with_description
378 378 get :index, :format => 'csv', :description => '1'
379 379 assert_response :success
380 380 assert_not_nil assigns(:issues)
381 381 assert_equal 'text/csv; header=present', @response.content_type
382 382 assert @response.body.starts_with?("#,")
383 383 lines = @response.body.chomp.split("\n")
384 384 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
385 385 end
386 386
387 387 def test_index_csv_with_spent_time_column
388 388 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :subject => 'test_index_csv_with_spent_time_column', :author_id => 2)
389 389 TimeEntry.create!(:project => issue.project, :issue => issue, :hours => 7.33, :user => User.find(2), :spent_on => Date.today)
390 390
391 391 get :index, :format => 'csv', :set_filter => '1', :c => %w(subject spent_hours)
392 392 assert_response :success
393 393 assert_equal 'text/csv; header=present', @response.content_type
394 394 lines = @response.body.chomp.split("\n")
395 395 assert_include "#{issue.id},#{issue.subject},7.33", lines
396 396 end
397 397
398 398 def test_index_csv_with_all_columns
399 399 get :index, :format => 'csv', :columns => 'all'
400 400 assert_response :success
401 401 assert_not_nil assigns(:issues)
402 402 assert_equal 'text/csv; header=present', @response.content_type
403 403 assert @response.body.starts_with?("#,")
404 404 lines = @response.body.chomp.split("\n")
405 405 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
406 406 end
407 407
408 408 def test_index_csv_with_multi_column_field
409 409 CustomField.find(1).update_attribute :multiple, true
410 410 issue = Issue.find(1)
411 411 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
412 412 issue.save!
413 413
414 414 get :index, :format => 'csv', :columns => 'all'
415 415 assert_response :success
416 416 lines = @response.body.chomp.split("\n")
417 417 assert lines.detect {|line| line.include?('"MySQL, Oracle"')}
418 418 end
419 419
420 420 def test_index_csv_big_5
421 421 with_settings :default_language => "zh-TW" do
422 422 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
423 423 str_big5 = "\xa4@\xa4\xeb"
424 424 if str_utf8.respond_to?(:force_encoding)
425 425 str_utf8.force_encoding('UTF-8')
426 426 str_big5.force_encoding('Big5')
427 427 end
428 428 issue = Issue.generate!(:subject => str_utf8)
429 429
430 430 get :index, :project_id => 1,
431 431 :f => ['subject'],
432 432 :op => '=', :values => [str_utf8],
433 433 :format => 'csv'
434 434 assert_equal 'text/csv; header=present', @response.content_type
435 435 lines = @response.body.chomp.split("\n")
436 436 s1 = "\xaa\xac\xbaA"
437 437 if str_utf8.respond_to?(:force_encoding)
438 438 s1.force_encoding('Big5')
439 439 end
440 440 assert lines[0].include?(s1)
441 441 assert lines[1].include?(str_big5)
442 442 end
443 443 end
444 444
445 445 def test_index_csv_cannot_convert_should_be_replaced_big_5
446 446 with_settings :default_language => "zh-TW" do
447 447 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
448 448 if str_utf8.respond_to?(:force_encoding)
449 449 str_utf8.force_encoding('UTF-8')
450 450 end
451 451 issue = Issue.generate!(:subject => str_utf8)
452 452
453 453 get :index, :project_id => 1,
454 454 :f => ['subject'],
455 455 :op => '=', :values => [str_utf8],
456 456 :c => ['status', 'subject'],
457 457 :format => 'csv',
458 458 :set_filter => 1
459 459 assert_equal 'text/csv; header=present', @response.content_type
460 460 lines = @response.body.chomp.split("\n")
461 461 s1 = "\xaa\xac\xbaA" # status
462 462 if str_utf8.respond_to?(:force_encoding)
463 463 s1.force_encoding('Big5')
464 464 end
465 465 assert lines[0].include?(s1)
466 466 s2 = lines[1].split(",")[2]
467 467 if s1.respond_to?(:force_encoding)
468 468 s3 = "\xa5H?" # subject
469 469 s3.force_encoding('Big5')
470 470 assert_equal s3, s2
471 471 elsif RUBY_PLATFORM == 'java'
472 472 assert_equal "??", s2
473 473 else
474 474 assert_equal "\xa5H???", s2
475 475 end
476 476 end
477 477 end
478 478
479 479 def test_index_csv_tw
480 480 with_settings :default_language => "zh-TW" do
481 481 str1 = "test_index_csv_tw"
482 482 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
483 483
484 484 get :index, :project_id => 1,
485 485 :f => ['subject'],
486 486 :op => '=', :values => [str1],
487 487 :c => ['estimated_hours', 'subject'],
488 488 :format => 'csv',
489 489 :set_filter => 1
490 490 assert_equal 'text/csv; header=present', @response.content_type
491 491 lines = @response.body.chomp.split("\n")
492 492 assert_equal "#{issue.id},1234.50,#{str1}", lines[1]
493 493 end
494 494 end
495 495
496 496 def test_index_csv_fr
497 497 with_settings :default_language => "fr" do
498 498 str1 = "test_index_csv_fr"
499 499 issue = Issue.generate!(:subject => str1, :estimated_hours => '1234.5')
500 500
501 501 get :index, :project_id => 1,
502 502 :f => ['subject'],
503 503 :op => '=', :values => [str1],
504 504 :c => ['estimated_hours', 'subject'],
505 505 :format => 'csv',
506 506 :set_filter => 1
507 507 assert_equal 'text/csv; header=present', @response.content_type
508 508 lines = @response.body.chomp.split("\n")
509 509 assert_equal "#{issue.id};1234,50;#{str1}", lines[1]
510 510 end
511 511 end
512 512
513 513 def test_index_pdf
514 514 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
515 515 with_settings :default_language => lang do
516 516
517 517 get :index
518 518 assert_response :success
519 519 assert_template 'index'
520 520
521 521 if lang == "ja"
522 522 if RUBY_PLATFORM != 'java'
523 523 assert_equal "CP932", l(:general_pdf_encoding)
524 524 end
525 525 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
526 526 next
527 527 end
528 528 end
529 529
530 530 get :index, :format => 'pdf'
531 531 assert_response :success
532 532 assert_not_nil assigns(:issues)
533 533 assert_equal 'application/pdf', @response.content_type
534 534
535 535 get :index, :project_id => 1, :format => 'pdf'
536 536 assert_response :success
537 537 assert_not_nil assigns(:issues)
538 538 assert_equal 'application/pdf', @response.content_type
539 539
540 540 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
541 541 assert_response :success
542 542 assert_not_nil assigns(:issues)
543 543 assert_equal 'application/pdf', @response.content_type
544 544 end
545 545 end
546 546 end
547 547
548 548 def test_index_pdf_with_query_grouped_by_list_custom_field
549 549 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
550 550 assert_response :success
551 551 assert_not_nil assigns(:issues)
552 552 assert_not_nil assigns(:issue_count_by_group)
553 553 assert_equal 'application/pdf', @response.content_type
554 554 end
555 555
556 556 def test_index_atom
557 557 get :index, :project_id => 'ecookbook', :format => 'atom'
558 558 assert_response :success
559 559 assert_template 'common/feed'
560 560 assert_equal 'application/atom+xml', response.content_type
561 561
562 562 assert_select 'feed' do
563 563 assert_select 'link[rel=self][href=?]', 'http://test.host/projects/ecookbook/issues.atom'
564 564 assert_select 'link[rel=alternate][href=?]', 'http://test.host/projects/ecookbook/issues'
565 565 assert_select 'entry link[href=?]', 'http://test.host/issues/1'
566 566 end
567 567 end
568 568
569 569 def test_index_sort
570 570 get :index, :sort => 'tracker,id:desc'
571 571 assert_response :success
572 572
573 573 sort_params = @request.session['issues_index_sort']
574 574 assert sort_params.is_a?(String)
575 575 assert_equal 'tracker,id:desc', sort_params
576 576
577 577 issues = assigns(:issues)
578 578 assert_not_nil issues
579 579 assert !issues.empty?
580 580 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
581 581 end
582 582
583 583 def test_index_sort_by_field_not_included_in_columns
584 584 Setting.issue_list_default_columns = %w(subject author)
585 585 get :index, :sort => 'tracker'
586 586 end
587 587
588 588 def test_index_sort_by_assigned_to
589 589 get :index, :sort => 'assigned_to'
590 590 assert_response :success
591 591 assignees = assigns(:issues).collect(&:assigned_to).compact
592 592 assert_equal assignees.sort, assignees
593 593 end
594 594
595 595 def test_index_sort_by_assigned_to_desc
596 596 get :index, :sort => 'assigned_to:desc'
597 597 assert_response :success
598 598 assignees = assigns(:issues).collect(&:assigned_to).compact
599 599 assert_equal assignees.sort.reverse, assignees
600 600 end
601 601
602 602 def test_index_group_by_assigned_to
603 603 get :index, :group_by => 'assigned_to', :sort => 'priority'
604 604 assert_response :success
605 605 end
606 606
607 607 def test_index_sort_by_author
608 608 get :index, :sort => 'author'
609 609 assert_response :success
610 610 authors = assigns(:issues).collect(&:author)
611 611 assert_equal authors.sort, authors
612 612 end
613 613
614 614 def test_index_sort_by_author_desc
615 615 get :index, :sort => 'author:desc'
616 616 assert_response :success
617 617 authors = assigns(:issues).collect(&:author)
618 618 assert_equal authors.sort.reverse, authors
619 619 end
620 620
621 621 def test_index_group_by_author
622 622 get :index, :group_by => 'author', :sort => 'priority'
623 623 assert_response :success
624 624 end
625 625
626 626 def test_index_sort_by_spent_hours
627 627 get :index, :sort => 'spent_hours:desc'
628 628 assert_response :success
629 629 hours = assigns(:issues).collect(&:spent_hours)
630 630 assert_equal hours.sort.reverse, hours
631 631 end
632 632
633 633 def test_index_sort_by_user_custom_field
634 634 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
635 635 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
636 636 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
637 637 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
638 638 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
639 639
640 640 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
641 641 assert_response :success
642 642
643 643 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
644 644 end
645 645
646 646 def test_index_with_columns
647 647 columns = ['tracker', 'subject', 'assigned_to']
648 648 get :index, :set_filter => 1, :c => columns
649 649 assert_response :success
650 650
651 651 # query should use specified columns
652 652 query = assigns(:query)
653 653 assert_kind_of Query, query
654 654 assert_equal columns, query.column_names.map(&:to_s)
655 655
656 656 # columns should be stored in session
657 657 assert_kind_of Hash, session[:query]
658 658 assert_kind_of Array, session[:query][:column_names]
659 659 assert_equal columns, session[:query][:column_names].map(&:to_s)
660 660
661 661 # ensure only these columns are kept in the selected columns list
662 662 assert_select 'select#selected_columns option' do
663 663 assert_select 'option', 3
664 664 assert_select 'option[value=tracker]'
665 665 assert_select 'option[value=project]', 0
666 666 end
667 667 end
668 668
669 669 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
670 670 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
671 671 get :index, :set_filter => 1
672 672
673 673 # query should use specified columns
674 674 query = assigns(:query)
675 675 assert_kind_of Query, query
676 676 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
677 677 end
678 678
679 679 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
680 680 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
681 681 columns = ['tracker', 'subject', 'assigned_to']
682 682 get :index, :set_filter => 1, :c => columns
683 683
684 684 # query should use specified columns
685 685 query = assigns(:query)
686 686 assert_kind_of Query, query
687 687 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
688 688 end
689 689
690 690 def test_index_with_custom_field_column
691 691 columns = %w(tracker subject cf_2)
692 692 get :index, :set_filter => 1, :c => columns
693 693 assert_response :success
694 694
695 695 # query should use specified columns
696 696 query = assigns(:query)
697 697 assert_kind_of Query, query
698 698 assert_equal columns, query.column_names.map(&:to_s)
699 699
700 700 assert_select 'table.issues td.cf_2.string'
701 701 end
702 702
703 703 def test_index_with_multi_custom_field_column
704 704 field = CustomField.find(1)
705 705 field.update_attribute :multiple, true
706 706 issue = Issue.find(1)
707 707 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
708 708 issue.save!
709 709
710 710 get :index, :set_filter => 1, :c => %w(tracker subject cf_1)
711 711 assert_response :success
712 712
713 713 assert_select 'table.issues td.cf_1', :text => 'MySQL, Oracle'
714 714 end
715 715
716 716 def test_index_with_multi_user_custom_field_column
717 717 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
718 718 :tracker_ids => [1], :is_for_all => true)
719 719 issue = Issue.find(1)
720 720 issue.custom_field_values = {field.id => ['2', '3']}
721 721 issue.save!
722 722
723 723 get :index, :set_filter => 1, :c => ['tracker', 'subject', "cf_#{field.id}"]
724 724 assert_response :success
725 725
726 726 assert_select "table.issues td.cf_#{field.id}" do
727 727 assert_select 'a', 2
728 728 assert_select 'a[href=?]', '/users/2', :text => 'John Smith'
729 729 assert_select 'a[href=?]', '/users/3', :text => 'Dave Lopper'
730 730 end
731 731 end
732 732
733 733 def test_index_with_date_column
734 734 with_settings :date_format => '%d/%m/%Y' do
735 735 Issue.find(1).update_attribute :start_date, '1987-08-24'
736 736
737 737 get :index, :set_filter => 1, :c => %w(start_date)
738 738
739 739 assert_select "table.issues td.start_date", :text => '24/08/1987'
740 740 end
741 741 end
742 742
743 743 def test_index_with_done_ratio_column
744 744 Issue.find(1).update_attribute :done_ratio, 40
745 745
746 746 get :index, :set_filter => 1, :c => %w(done_ratio)
747 747
748 748 assert_select 'table.issues td.done_ratio' do
749 749 assert_select 'table.progress' do
750 750 assert_select 'td.closed[style=?]', 'width: 40%;'
751 751 end
752 752 end
753 753 end
754 754
755 755 def test_index_with_spent_hours_column
756 756 get :index, :set_filter => 1, :c => %w(subject spent_hours)
757 757
758 758 assert_select 'table.issues tr#issue-3 td.spent_hours', :text => '1.00'
759 759 end
760 760
761 761 def test_index_should_not_show_spent_hours_column_without_permission
762 762 Role.anonymous.remove_permission! :view_time_entries
763 763 get :index, :set_filter => 1, :c => %w(subject spent_hours)
764 764
765 765 assert_select 'td.spent_hours', 0
766 766 end
767 767
768 768 def test_index_with_fixed_version_column
769 769 get :index, :set_filter => 1, :c => %w(fixed_version)
770 770
771 771 assert_select 'table.issues td.fixed_version' do
772 772 assert_select 'a[href=?]', '/versions/2', :text => '1.0'
773 773 end
774 774 end
775 775
776 776 def test_index_with_relations_column
777 777 IssueRelation.delete_all
778 778 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(7))
779 779 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(8), :issue_to => Issue.find(1))
780 780 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(1), :issue_to => Issue.find(11))
781 781 IssueRelation.create!(:relation_type => "blocks", :issue_from => Issue.find(12), :issue_to => Issue.find(2))
782 782
783 783 get :index, :set_filter => 1, :c => %w(subject relations)
784 784 assert_response :success
785 785 assert_select "tr#issue-1 td.relations" do
786 786 assert_select "span", 3
787 787 assert_select "span", :text => "Related to #7"
788 788 assert_select "span", :text => "Related to #8"
789 789 assert_select "span", :text => "Blocks #11"
790 790 end
791 791 assert_select "tr#issue-2 td.relations" do
792 792 assert_select "span", 1
793 793 assert_select "span", :text => "Blocked by #12"
794 794 end
795 795 assert_select "tr#issue-3 td.relations" do
796 796 assert_select "span", 0
797 797 end
798 798
799 799 get :index, :set_filter => 1, :c => %w(relations), :format => 'csv'
800 800 assert_response :success
801 801 assert_equal 'text/csv; header=present', response.content_type
802 802 lines = response.body.chomp.split("\n")
803 803 assert_include '1,"Related to #7, Related to #8, Blocks #11"', lines
804 804 assert_include '2,Blocked by #12', lines
805 805 assert_include '3,""', lines
806 806
807 807 get :index, :set_filter => 1, :c => %w(subject relations), :format => 'pdf'
808 808 assert_response :success
809 809 assert_equal 'application/pdf', response.content_type
810 810 end
811 811
812 812 def test_index_send_html_if_query_is_invalid
813 813 get :index, :f => ['start_date'], :op => {:start_date => '='}
814 814 assert_equal 'text/html', @response.content_type
815 815 assert_template 'index'
816 816 end
817 817
818 818 def test_index_send_nothing_if_query_is_invalid
819 819 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
820 820 assert_equal 'text/csv', @response.content_type
821 821 assert @response.body.blank?
822 822 end
823 823
824 824 def test_show_by_anonymous
825 825 get :show, :id => 1
826 826 assert_response :success
827 827 assert_template 'show'
828 828 assert_equal Issue.find(1), assigns(:issue)
829 829
830 830 # anonymous role is allowed to add a note
831 831 assert_select 'form#issue-form' do
832 832 assert_select 'fieldset' do
833 833 assert_select 'legend', :text => 'Notes'
834 834 assert_select 'textarea[name=?]', 'issue[notes]'
835 835 end
836 836 end
837 837
838 838 assert_select 'title', :text => "Bug #1: Can&#x27;t print recipes - eCookbook - Redmine"
839 839 end
840 840
841 841 def test_show_by_manager
842 842 @request.session[:user_id] = 2
843 843 get :show, :id => 1
844 844 assert_response :success
845 845
846 846 assert_select 'a', :text => /Quote/
847 847
848 848 assert_select 'form#issue-form' do
849 849 assert_select 'fieldset' do
850 850 assert_select 'legend', :text => 'Change properties'
851 851 assert_select 'input[name=?]', 'issue[subject]'
852 852 end
853 853 assert_select 'fieldset' do
854 854 assert_select 'legend', :text => 'Log time'
855 855 assert_select 'input[name=?]', 'time_entry[hours]'
856 856 end
857 857 assert_select 'fieldset' do
858 858 assert_select 'legend', :text => 'Notes'
859 859 assert_select 'textarea[name=?]', 'issue[notes]'
860 860 end
861 861 end
862 862 end
863 863
864 864 def test_show_should_display_update_form
865 865 @request.session[:user_id] = 2
866 866 get :show, :id => 1
867 867 assert_response :success
868 868
869 869 assert_tag 'form', :attributes => {:id => 'issue-form'}
870 870 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
871 871 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
872 872 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
873 873 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
874 874 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
875 875 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
876 876 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
877 877 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
878 878 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
879 879 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
880 880 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
881 881 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
882 882 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
883 883 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
884 884 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
885 885 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
886 886 assert_tag 'textarea', :attributes => {:name => 'issue[notes]'}
887 887 end
888 888
889 889 def test_show_should_display_update_form_with_minimal_permissions
890 890 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
891 891 WorkflowTransition.delete_all :role_id => 1
892 892
893 893 @request.session[:user_id] = 2
894 894 get :show, :id => 1
895 895 assert_response :success
896 896
897 897 assert_tag 'form', :attributes => {:id => 'issue-form'}
898 898 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
899 899 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
900 900 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
901 901 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
902 902 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
903 903 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
904 904 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
905 905 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
906 906 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
907 907 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
908 908 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
909 909 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
910 910 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
911 911 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
912 912 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
913 913 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
914 914 assert_tag 'textarea', :attributes => {:name => 'issue[notes]'}
915 915 end
916 916
917 917 def test_show_should_display_update_form_with_workflow_permissions
918 918 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
919 919
920 920 @request.session[:user_id] = 2
921 921 get :show, :id => 1
922 922 assert_response :success
923 923
924 924 assert_tag 'form', :attributes => {:id => 'issue-form'}
925 925 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
926 926 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
927 927 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
928 928 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
929 929 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
930 930 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
931 931 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
932 932 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
933 933 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
934 934 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
935 935 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
936 936 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
937 937 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
938 938 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
939 939 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
940 940 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
941 941 assert_tag 'textarea', :attributes => {:name => 'issue[notes]'}
942 942 end
943 943
944 944 def test_show_should_not_display_update_form_without_permissions
945 945 Role.find(1).update_attribute :permissions, [:view_issues]
946 946
947 947 @request.session[:user_id] = 2
948 948 get :show, :id => 1
949 949 assert_response :success
950 950
951 951 assert_select 'form#issue-form', 0
952 952 end
953 953
954 954 def test_update_form_should_not_display_inactive_enumerations
955 955 assert !IssuePriority.find(15).active?
956 956
957 957 @request.session[:user_id] = 2
958 958 get :show, :id => 1
959 959 assert_response :success
960 960
961 961 assert_select 'form#issue-form' do
962 962 assert_select 'select[name=?]', 'issue[priority_id]' do
963 963 assert_select 'option[value=4]'
964 964 assert_select 'option[value=15]', 0
965 965 end
966 966 end
967 967 end
968 968
969 969 def test_update_form_should_allow_attachment_upload
970 970 @request.session[:user_id] = 2
971 971 get :show, :id => 1
972 972
973 973 assert_select 'form#issue-form[method=post][enctype=multipart/form-data]' do
974 974 assert_select 'input[type=file][name=?]', 'attachments[1][file]'
975 975 end
976 976 end
977 977
978 978 def test_show_should_deny_anonymous_access_without_permission
979 979 Role.anonymous.remove_permission!(:view_issues)
980 980 get :show, :id => 1
981 981 assert_response :redirect
982 982 end
983 983
984 984 def test_show_should_deny_anonymous_access_to_private_issue
985 985 Issue.update_all(["is_private = ?", true], "id = 1")
986 986 get :show, :id => 1
987 987 assert_response :redirect
988 988 end
989 989
990 990 def test_show_should_deny_non_member_access_without_permission
991 991 Role.non_member.remove_permission!(:view_issues)
992 992 @request.session[:user_id] = 9
993 993 get :show, :id => 1
994 994 assert_response 403
995 995 end
996 996
997 997 def test_show_should_deny_non_member_access_to_private_issue
998 998 Issue.update_all(["is_private = ?", true], "id = 1")
999 999 @request.session[:user_id] = 9
1000 1000 get :show, :id => 1
1001 1001 assert_response 403
1002 1002 end
1003 1003
1004 1004 def test_show_should_deny_member_access_without_permission
1005 1005 Role.find(1).remove_permission!(:view_issues)
1006 1006 @request.session[:user_id] = 2
1007 1007 get :show, :id => 1
1008 1008 assert_response 403
1009 1009 end
1010 1010
1011 1011 def test_show_should_deny_member_access_to_private_issue_without_permission
1012 1012 Issue.update_all(["is_private = ?", true], "id = 1")
1013 1013 @request.session[:user_id] = 3
1014 1014 get :show, :id => 1
1015 1015 assert_response 403
1016 1016 end
1017 1017
1018 1018 def test_show_should_allow_author_access_to_private_issue
1019 1019 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
1020 1020 @request.session[:user_id] = 3
1021 1021 get :show, :id => 1
1022 1022 assert_response :success
1023 1023 end
1024 1024
1025 1025 def test_show_should_allow_assignee_access_to_private_issue
1026 1026 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
1027 1027 @request.session[:user_id] = 3
1028 1028 get :show, :id => 1
1029 1029 assert_response :success
1030 1030 end
1031 1031
1032 1032 def test_show_should_allow_member_access_to_private_issue_with_permission
1033 1033 Issue.update_all(["is_private = ?", true], "id = 1")
1034 1034 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
1035 1035 @request.session[:user_id] = 3
1036 1036 get :show, :id => 1
1037 1037 assert_response :success
1038 1038 end
1039 1039
1040 1040 def test_show_should_not_disclose_relations_to_invisible_issues
1041 1041 Setting.cross_project_issue_relations = '1'
1042 1042 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
1043 1043 # Relation to a private project issue
1044 1044 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
1045 1045
1046 1046 get :show, :id => 1
1047 1047 assert_response :success
1048 1048
1049 1049 assert_select 'div#relations' do
1050 1050 assert_select 'a', :text => /#2$/
1051 1051 assert_select 'a', :text => /#4$/, :count => 0
1052 1052 end
1053 1053 end
1054 1054
1055 1055 def test_show_should_list_subtasks
1056 1056 Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1057 1057
1058 1058 get :show, :id => 1
1059 1059 assert_response :success
1060 1060
1061 1061 assert_select 'div#issue_tree' do
1062 1062 assert_select 'td.subject', :text => /Child Issue/
1063 1063 end
1064 1064 end
1065 1065
1066 1066 def test_show_should_list_parents
1067 1067 issue = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
1068 1068
1069 1069 get :show, :id => issue.id
1070 1070 assert_response :success
1071 1071
1072 1072 assert_select 'div.subject' do
1073 1073 assert_select 'h3', 'Child Issue'
1074 1074 assert_select 'a[href=/issues/1]'
1075 1075 end
1076 1076 end
1077 1077
1078 1078 def test_show_should_not_display_prev_next_links_without_query_in_session
1079 1079 get :show, :id => 1
1080 1080 assert_response :success
1081 1081 assert_nil assigns(:prev_issue_id)
1082 1082 assert_nil assigns(:next_issue_id)
1083 1083
1084 1084 assert_select 'div.next-prev-links', 0
1085 1085 end
1086 1086
1087 1087 def test_show_should_display_prev_next_links_with_query_in_session
1088 1088 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1089 1089 @request.session['issues_index_sort'] = 'id'
1090 1090
1091 1091 with_settings :display_subprojects_issues => '0' do
1092 1092 get :show, :id => 3
1093 1093 end
1094 1094
1095 1095 assert_response :success
1096 1096 # Previous and next issues for all projects
1097 1097 assert_equal 2, assigns(:prev_issue_id)
1098 1098 assert_equal 5, assigns(:next_issue_id)
1099 1099
1100 1100 count = Issue.open.visible.count
1101 1101
1102 1102 assert_select 'div.next-prev-links' do
1103 1103 assert_select 'a[href=/issues/2]', :text => /Previous/
1104 1104 assert_select 'a[href=/issues/5]', :text => /Next/
1105 1105 assert_select 'span.position', :text => "3 of #{count}"
1106 1106 end
1107 1107 end
1108 1108
1109 1109 def test_show_should_display_prev_next_links_with_saved_query_in_session
1110 1110 query = Query.create!(:name => 'test', :is_public => true, :user_id => 1,
1111 1111 :filters => {'status_id' => {:values => ['5'], :operator => '='}},
1112 1112 :sort_criteria => [['id', 'asc']])
1113 1113 @request.session[:query] = {:id => query.id, :project_id => nil}
1114 1114
1115 1115 get :show, :id => 11
1116 1116
1117 1117 assert_response :success
1118 1118 assert_equal query, assigns(:query)
1119 1119 # Previous and next issues for all projects
1120 1120 assert_equal 8, assigns(:prev_issue_id)
1121 1121 assert_equal 12, assigns(:next_issue_id)
1122 1122
1123 1123 assert_select 'div.next-prev-links' do
1124 1124 assert_select 'a[href=/issues/8]', :text => /Previous/
1125 1125 assert_select 'a[href=/issues/12]', :text => /Next/
1126 1126 end
1127 1127 end
1128 1128
1129 1129 def test_show_should_display_prev_next_links_with_query_and_sort_on_association
1130 1130 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => nil}
1131 1131
1132 1132 %w(project tracker status priority author assigned_to category fixed_version).each do |assoc_sort|
1133 1133 @request.session['issues_index_sort'] = assoc_sort
1134 1134
1135 1135 get :show, :id => 3
1136 1136 assert_response :success, "Wrong response status for #{assoc_sort} sort"
1137 1137
1138 1138 assert_select 'div.next-prev-links' do
1139 1139 assert_select 'a', :text => /Previous/
1140 1140 assert_select 'a', :text => /Next/
1141 1141 end
1142 1142 end
1143 1143 end
1144 1144
1145 1145 def test_show_should_display_prev_next_links_with_project_query_in_session
1146 1146 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1147 1147 @request.session['issues_index_sort'] = 'id'
1148 1148
1149 1149 with_settings :display_subprojects_issues => '0' do
1150 1150 get :show, :id => 3
1151 1151 end
1152 1152
1153 1153 assert_response :success
1154 1154 # Previous and next issues inside project
1155 1155 assert_equal 2, assigns(:prev_issue_id)
1156 1156 assert_equal 7, assigns(:next_issue_id)
1157 1157
1158 1158 assert_select 'div.next-prev-links' do
1159 1159 assert_select 'a[href=/issues/2]', :text => /Previous/
1160 1160 assert_select 'a[href=/issues/7]', :text => /Next/
1161 1161 end
1162 1162 end
1163 1163
1164 1164 def test_show_should_not_display_prev_link_for_first_issue
1165 1165 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'o'}}, :project_id => 1}
1166 1166 @request.session['issues_index_sort'] = 'id'
1167 1167
1168 1168 with_settings :display_subprojects_issues => '0' do
1169 1169 get :show, :id => 1
1170 1170 end
1171 1171
1172 1172 assert_response :success
1173 1173 assert_nil assigns(:prev_issue_id)
1174 1174 assert_equal 2, assigns(:next_issue_id)
1175 1175
1176 1176 assert_select 'div.next-prev-links' do
1177 1177 assert_select 'a', :text => /Previous/, :count => 0
1178 1178 assert_select 'a[href=/issues/2]', :text => /Next/
1179 1179 end
1180 1180 end
1181 1181
1182 1182 def test_show_should_not_display_prev_next_links_for_issue_not_in_query_results
1183 1183 @request.session[:query] = {:filters => {'status_id' => {:values => [''], :operator => 'c'}}, :project_id => 1}
1184 1184 @request.session['issues_index_sort'] = 'id'
1185 1185
1186 1186 get :show, :id => 1
1187 1187
1188 1188 assert_response :success
1189 1189 assert_nil assigns(:prev_issue_id)
1190 1190 assert_nil assigns(:next_issue_id)
1191 1191
1192 1192 assert_select 'a', :text => /Previous/, :count => 0
1193 1193 assert_select 'a', :text => /Next/, :count => 0
1194 1194 end
1195 1195
1196 1196 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1197 1197 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1198 1198 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1199 1199 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1200 1200 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1201 1201 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1202 1202
1203 1203 query = Query.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {},
1204 1204 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1205 1205 @request.session[:query] = {:id => query.id, :project_id => nil}
1206 1206
1207 1207 get :show, :id => 3
1208 1208 assert_response :success
1209 1209
1210 1210 assert_equal 2, assigns(:prev_issue_id)
1211 1211 assert_equal 1, assigns(:next_issue_id)
1212 1212
1213 1213 assert_select 'div.next-prev-links' do
1214 1214 assert_select 'a[href=/issues/2]', :text => /Previous/
1215 1215 assert_select 'a[href=/issues/1]', :text => /Next/
1216 1216 end
1217 1217 end
1218 1218
1219 1219 def test_show_should_display_link_to_the_assignee
1220 1220 get :show, :id => 2
1221 1221 assert_response :success
1222 1222 assert_select '.assigned-to' do
1223 1223 assert_select 'a[href=/users/3]'
1224 1224 end
1225 1225 end
1226 1226
1227 1227 def test_show_should_display_visible_changesets_from_other_projects
1228 1228 project = Project.find(2)
1229 1229 issue = project.issues.first
1230 1230 issue.changeset_ids = [102]
1231 1231 issue.save!
1232 1232 # changesets from other projects should be displayed even if repository
1233 1233 # is disabled on issue's project
1234 1234 project.disable_module! :repository
1235 1235
1236 1236 @request.session[:user_id] = 2
1237 1237 get :show, :id => issue.id
1238 1238
1239 1239 assert_select 'a[href=?]', '/projects/ecookbook/repository/revisions/3'
1240 1240 end
1241 1241
1242 1242 def test_show_should_display_watchers
1243 1243 @request.session[:user_id] = 2
1244 1244 Issue.find(1).add_watcher User.find(2)
1245 1245
1246 1246 get :show, :id => 1
1247 1247 assert_select 'div#watchers ul' do
1248 1248 assert_select 'li' do
1249 1249 assert_select 'a[href=/users/2]'
1250 1250 assert_select 'a img[alt=Delete]'
1251 1251 end
1252 1252 end
1253 1253 end
1254 1254
1255 1255 def test_show_should_display_watchers_with_gravatars
1256 1256 @request.session[:user_id] = 2
1257 1257 Issue.find(1).add_watcher User.find(2)
1258 1258
1259 1259 with_settings :gravatar_enabled => '1' do
1260 1260 get :show, :id => 1
1261 1261 end
1262 1262
1263 1263 assert_select 'div#watchers ul' do
1264 1264 assert_select 'li' do
1265 1265 assert_select 'img.gravatar'
1266 1266 assert_select 'a[href=/users/2]'
1267 1267 assert_select 'a img[alt=Delete]'
1268 1268 end
1269 1269 end
1270 1270 end
1271 1271
1272 1272 def test_show_with_thumbnails_enabled_should_display_thumbnails
1273 1273 @request.session[:user_id] = 2
1274 1274
1275 1275 with_settings :thumbnails_enabled => '1' do
1276 1276 get :show, :id => 14
1277 1277 assert_response :success
1278 1278 end
1279 1279
1280 1280 assert_select 'div.thumbnails' do
1281 1281 assert_select 'a[href=/attachments/16/testfile.png]' do
1282 1282 assert_select 'img[src=/attachments/thumbnail/16]'
1283 1283 end
1284 1284 end
1285 1285 end
1286 1286
1287 1287 def test_show_with_thumbnails_disabled_should_not_display_thumbnails
1288 1288 @request.session[:user_id] = 2
1289 1289
1290 1290 with_settings :thumbnails_enabled => '0' do
1291 1291 get :show, :id => 14
1292 1292 assert_response :success
1293 1293 end
1294 1294
1295 1295 assert_select 'div.thumbnails', 0
1296 1296 end
1297 1297
1298 1298 def test_show_with_multi_custom_field
1299 1299 field = CustomField.find(1)
1300 1300 field.update_attribute :multiple, true
1301 1301 issue = Issue.find(1)
1302 1302 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
1303 1303 issue.save!
1304 1304
1305 1305 get :show, :id => 1
1306 1306 assert_response :success
1307 1307
1308 1308 assert_select 'td', :text => 'MySQL, Oracle'
1309 1309 end
1310 1310
1311 1311 def test_show_with_multi_user_custom_field
1312 1312 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1313 1313 :tracker_ids => [1], :is_for_all => true)
1314 1314 issue = Issue.find(1)
1315 1315 issue.custom_field_values = {field.id => ['2', '3']}
1316 1316 issue.save!
1317 1317
1318 1318 get :show, :id => 1
1319 1319 assert_response :success
1320 1320
1321 1321 # TODO: should display links
1322 1322 assert_select 'td', :text => 'Dave Lopper, John Smith'
1323 1323 end
1324 1324
1325 1325 def test_show_should_display_private_notes_with_permission_only
1326 1326 journal = Journal.create!(:journalized => Issue.find(2), :notes => 'Privates notes', :private_notes => true, :user_id => 1)
1327 1327 @request.session[:user_id] = 2
1328 1328
1329 1329 get :show, :id => 2
1330 1330 assert_response :success
1331 1331 assert_include journal, assigns(:journals)
1332 1332
1333 1333 Role.find(1).remove_permission! :view_private_notes
1334 1334 get :show, :id => 2
1335 1335 assert_response :success
1336 1336 assert_not_include journal, assigns(:journals)
1337 1337 end
1338 1338
1339 1339 def test_show_atom
1340 1340 get :show, :id => 2, :format => 'atom'
1341 1341 assert_response :success
1342 1342 assert_template 'journals/index'
1343 1343 # Inline image
1344 1344 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
1345 1345 end
1346 1346
1347 1347 def test_show_export_to_pdf
1348 1348 get :show, :id => 3, :format => 'pdf'
1349 1349 assert_response :success
1350 1350 assert_equal 'application/pdf', @response.content_type
1351 1351 assert @response.body.starts_with?('%PDF')
1352 1352 assert_not_nil assigns(:issue)
1353 1353 end
1354 1354
1355 1355 def test_show_export_to_pdf_with_ancestors
1356 1356 issue = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1357 1357
1358 1358 get :show, :id => issue.id, :format => 'pdf'
1359 1359 assert_response :success
1360 1360 assert_equal 'application/pdf', @response.content_type
1361 1361 assert @response.body.starts_with?('%PDF')
1362 1362 end
1363 1363
1364 1364 def test_show_export_to_pdf_with_descendants
1365 1365 c1 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1366 1366 c2 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => 1)
1367 1367 c3 = Issue.generate!(:project_id => 1, :author_id => 2, :tracker_id => 1, :subject => 'child', :parent_issue_id => c1.id)
1368 1368
1369 1369 get :show, :id => 1, :format => 'pdf'
1370 1370 assert_response :success
1371 1371 assert_equal 'application/pdf', @response.content_type
1372 1372 assert @response.body.starts_with?('%PDF')
1373 1373 end
1374 1374
1375 1375 def test_show_export_to_pdf_with_journals
1376 1376 get :show, :id => 1, :format => 'pdf'
1377 1377 assert_response :success
1378 1378 assert_equal 'application/pdf', @response.content_type
1379 1379 assert @response.body.starts_with?('%PDF')
1380 1380 end
1381 1381
1382 1382 def test_show_export_to_pdf_with_changesets
1383 1383 Issue.find(3).changesets = Changeset.find_all_by_id(100, 101, 102)
1384 1384
1385 1385 get :show, :id => 3, :format => 'pdf'
1386 1386 assert_response :success
1387 1387 assert_equal 'application/pdf', @response.content_type
1388 1388 assert @response.body.starts_with?('%PDF')
1389 1389 end
1390 1390
1391 1391 def test_get_new
1392 1392 @request.session[:user_id] = 2
1393 1393 get :new, :project_id => 1, :tracker_id => 1
1394 1394 assert_response :success
1395 1395 assert_template 'new'
1396 1396
1397 1397 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
1398 1398 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1399 1399 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1400 1400 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1401 1401 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1402 1402 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1403 1403 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1404 1404 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1405 1405 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1406 1406 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1407 1407 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1408 1408 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1409 1409 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1410 1410 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1411 1411 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1412 1412 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1413 1413
1414 1414 # Be sure we don't display inactive IssuePriorities
1415 1415 assert ! IssuePriority.find(15).active?
1416 1416 assert_no_tag :option, :attributes => {:value => '15'},
1417 1417 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1418 1418 end
1419 1419
1420 1420 def test_get_new_with_minimal_permissions
1421 1421 Role.find(1).update_attribute :permissions, [:add_issues]
1422 1422 WorkflowTransition.delete_all :role_id => 1
1423 1423
1424 1424 @request.session[:user_id] = 2
1425 1425 get :new, :project_id => 1, :tracker_id => 1
1426 1426 assert_response :success
1427 1427 assert_template 'new'
1428 1428
1429 1429 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
1430 1430 assert_no_tag 'select', :attributes => {:name => 'issue[project_id]'}
1431 1431 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
1432 1432 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
1433 1433 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
1434 1434 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
1435 1435 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
1436 1436 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
1437 1437 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
1438 1438 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
1439 1439 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
1440 1440 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
1441 1441 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
1442 1442 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
1443 1443 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
1444 1444 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
1445 1445 end
1446 1446
1447 1447 def test_get_new_with_list_custom_field
1448 1448 @request.session[:user_id] = 2
1449 1449 get :new, :project_id => 1, :tracker_id => 1
1450 1450 assert_response :success
1451 1451 assert_template 'new'
1452 1452
1453 1453 assert_select 'select.list_cf[name=?]', 'issue[custom_field_values][1]' do
1454 1454 assert_select 'option', 4
1455 1455 assert_select 'option[value=MySQL]', :text => 'MySQL'
1456 1456 end
1457 1457 end
1458 1458
1459 1459 def test_get_new_with_multi_custom_field
1460 1460 field = IssueCustomField.find(1)
1461 1461 field.update_attribute :multiple, true
1462 1462
1463 1463 @request.session[:user_id] = 2
1464 1464 get :new, :project_id => 1, :tracker_id => 1
1465 1465 assert_response :success
1466 1466 assert_template 'new'
1467 1467
1468 1468 assert_select 'select[name=?][multiple=multiple]', 'issue[custom_field_values][1][]' do
1469 1469 assert_select 'option', 3
1470 1470 assert_select 'option[value=MySQL]', :text => 'MySQL'
1471 1471 end
1472 1472 assert_select 'input[name=?][type=hidden][value=?]', 'issue[custom_field_values][1][]', ''
1473 1473 end
1474 1474
1475 1475 def test_get_new_with_multi_user_custom_field
1476 1476 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1477 1477 :tracker_ids => [1], :is_for_all => true)
1478 1478
1479 1479 @request.session[:user_id] = 2
1480 1480 get :new, :project_id => 1, :tracker_id => 1
1481 1481 assert_response :success
1482 1482 assert_template 'new'
1483 1483
1484 1484 assert_select 'select[name=?][multiple=multiple]', "issue[custom_field_values][#{field.id}][]" do
1485 1485 assert_select 'option', Project.find(1).users.count
1486 1486 assert_select 'option[value=2]', :text => 'John Smith'
1487 1487 end
1488 1488 assert_select 'input[name=?][type=hidden][value=?]', "issue[custom_field_values][#{field.id}][]", ''
1489 1489 end
1490 1490
1491 1491 def test_get_new_with_date_custom_field
1492 1492 field = IssueCustomField.create!(:name => 'Date', :field_format => 'date', :tracker_ids => [1], :is_for_all => true)
1493 1493
1494 1494 @request.session[:user_id] = 2
1495 1495 get :new, :project_id => 1, :tracker_id => 1
1496 1496 assert_response :success
1497 1497
1498 1498 assert_select 'input[name=?]', "issue[custom_field_values][#{field.id}]"
1499 1499 end
1500 1500
1501 1501 def test_get_new_with_text_custom_field
1502 1502 field = IssueCustomField.create!(:name => 'Text', :field_format => 'text', :tracker_ids => [1], :is_for_all => true)
1503 1503
1504 1504 @request.session[:user_id] = 2
1505 1505 get :new, :project_id => 1, :tracker_id => 1
1506 1506 assert_response :success
1507 1507
1508 1508 assert_select 'textarea[name=?]', "issue[custom_field_values][#{field.id}]"
1509 1509 end
1510 1510
1511 1511 def test_get_new_without_default_start_date_is_creation_date
1512 1512 Setting.default_issue_start_date_to_creation_date = 0
1513 1513
1514 1514 @request.session[:user_id] = 2
1515 1515 get :new, :project_id => 1, :tracker_id => 1
1516 1516 assert_response :success
1517 1517 assert_template 'new'
1518 1518
1519 1519 assert_select 'input[name=?]', 'issue[start_date]'
1520 1520 assert_select 'input[name=?][value]', 'issue[start_date]', 0
1521 1521 end
1522 1522
1523 1523 def test_get_new_with_default_start_date_is_creation_date
1524 1524 Setting.default_issue_start_date_to_creation_date = 1
1525 1525
1526 1526 @request.session[:user_id] = 2
1527 1527 get :new, :project_id => 1, :tracker_id => 1
1528 1528 assert_response :success
1529 1529 assert_template 'new'
1530 1530
1531 1531 assert_select 'input[name=?][value=?]', 'issue[start_date]', Date.today.to_s
1532 1532 end
1533 1533
1534 1534 def test_get_new_form_should_allow_attachment_upload
1535 1535 @request.session[:user_id] = 2
1536 1536 get :new, :project_id => 1, :tracker_id => 1
1537 1537
1538 1538 assert_select 'form[id=issue-form][method=post][enctype=multipart/form-data]' do
1539 1539 assert_select 'input[name=?][type=file]', 'attachments[1][file]'
1540 1540 assert_select 'input[name=?][maxlength=255]', 'attachments[1][description]'
1541 1541 end
1542 1542 end
1543 1543
1544 1544 def test_get_new_should_prefill_the_form_from_params
1545 1545 @request.session[:user_id] = 2
1546 1546 get :new, :project_id => 1,
1547 1547 :issue => {:tracker_id => 3, :description => 'Prefilled', :custom_field_values => {'2' => 'Custom field value'}}
1548 1548
1549 1549 issue = assigns(:issue)
1550 1550 assert_equal 3, issue.tracker_id
1551 1551 assert_equal 'Prefilled', issue.description
1552 1552 assert_equal 'Custom field value', issue.custom_field_value(2)
1553 1553
1554 1554 assert_select 'select[name=?]', 'issue[tracker_id]' do
1555 1555 assert_select 'option[value=3][selected=selected]'
1556 1556 end
1557 1557 assert_select 'textarea[name=?]', 'issue[description]', :text => /Prefilled/
1558 1558 assert_select 'input[name=?][value=?]', 'issue[custom_field_values][2]', 'Custom field value'
1559 1559 end
1560 1560
1561 1561 def test_get_new_should_mark_required_fields
1562 1562 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1563 1563 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1564 1564 WorkflowPermission.delete_all
1565 1565 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1566 1566 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1567 1567 @request.session[:user_id] = 2
1568 1568
1569 1569 get :new, :project_id => 1
1570 1570 assert_response :success
1571 1571 assert_template 'new'
1572 1572
1573 1573 assert_select 'label[for=issue_start_date]' do
1574 1574 assert_select 'span[class=required]', 0
1575 1575 end
1576 1576 assert_select 'label[for=issue_due_date]' do
1577 1577 assert_select 'span[class=required]'
1578 1578 end
1579 1579 assert_select 'label[for=?]', "issue_custom_field_values_#{cf1.id}" do
1580 1580 assert_select 'span[class=required]', 0
1581 1581 end
1582 1582 assert_select 'label[for=?]', "issue_custom_field_values_#{cf2.id}" do
1583 1583 assert_select 'span[class=required]'
1584 1584 end
1585 1585 end
1586 1586
1587 1587 def test_get_new_should_not_display_readonly_fields
1588 1588 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1589 1589 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1590 1590 WorkflowPermission.delete_all
1591 1591 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1592 1592 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1593 1593 @request.session[:user_id] = 2
1594 1594
1595 1595 get :new, :project_id => 1
1596 1596 assert_response :success
1597 1597 assert_template 'new'
1598 1598
1599 1599 assert_select 'input[name=?]', 'issue[start_date]'
1600 1600 assert_select 'input[name=?]', 'issue[due_date]', 0
1601 1601 assert_select 'input[name=?]', "issue[custom_field_values][#{cf1.id}]"
1602 1602 assert_select 'input[name=?]', "issue[custom_field_values][#{cf2.id}]", 0
1603 1603 end
1604 1604
1605 1605 def test_get_new_without_tracker_id
1606 1606 @request.session[:user_id] = 2
1607 1607 get :new, :project_id => 1
1608 1608 assert_response :success
1609 1609 assert_template 'new'
1610 1610
1611 1611 issue = assigns(:issue)
1612 1612 assert_not_nil issue
1613 1613 assert_equal Project.find(1).trackers.first, issue.tracker
1614 1614 end
1615 1615
1616 1616 def test_get_new_with_no_default_status_should_display_an_error
1617 1617 @request.session[:user_id] = 2
1618 1618 IssueStatus.delete_all
1619 1619
1620 1620 get :new, :project_id => 1
1621 1621 assert_response 500
1622 1622 assert_error_tag :content => /No default issue/
1623 1623 end
1624 1624
1625 1625 def test_get_new_with_no_tracker_should_display_an_error
1626 1626 @request.session[:user_id] = 2
1627 1627 Tracker.delete_all
1628 1628
1629 1629 get :new, :project_id => 1
1630 1630 assert_response 500
1631 1631 assert_error_tag :content => /No tracker/
1632 1632 end
1633 1633
1634 1634 def test_update_new_form
1635 1635 @request.session[:user_id] = 2
1636 1636 xhr :post, :new, :project_id => 1,
1637 1637 :issue => {:tracker_id => 2,
1638 1638 :subject => 'This is the test_new issue',
1639 1639 :description => 'This is the description',
1640 1640 :priority_id => 5}
1641 1641 assert_response :success
1642 1642 assert_template 'update_form'
1643 1643 assert_template 'form'
1644 1644 assert_equal 'text/javascript', response.content_type
1645 1645
1646 1646 issue = assigns(:issue)
1647 1647 assert_kind_of Issue, issue
1648 1648 assert_equal 1, issue.project_id
1649 1649 assert_equal 2, issue.tracker_id
1650 1650 assert_equal 'This is the test_new issue', issue.subject
1651 1651 end
1652 1652
1653 1653 def test_update_new_form_should_propose_transitions_based_on_initial_status
1654 1654 @request.session[:user_id] = 2
1655 1655 WorkflowTransition.delete_all
1656 1656 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
1657 1657 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
1658 1658 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
1659 1659
1660 1660 xhr :post, :new, :project_id => 1,
1661 1661 :issue => {:tracker_id => 1,
1662 1662 :status_id => 5,
1663 1663 :subject => 'This is an issue'}
1664 1664
1665 1665 assert_equal 5, assigns(:issue).status_id
1666 1666 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
1667 1667 end
1668 1668
1669 1669 def test_post_create
1670 1670 @request.session[:user_id] = 2
1671 1671 assert_difference 'Issue.count' do
1672 1672 post :create, :project_id => 1,
1673 1673 :issue => {:tracker_id => 3,
1674 1674 :status_id => 2,
1675 1675 :subject => 'This is the test_new issue',
1676 1676 :description => 'This is the description',
1677 1677 :priority_id => 5,
1678 1678 :start_date => '2010-11-07',
1679 1679 :estimated_hours => '',
1680 1680 :custom_field_values => {'2' => 'Value for field 2'}}
1681 1681 end
1682 1682 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1683 1683
1684 1684 issue = Issue.find_by_subject('This is the test_new issue')
1685 1685 assert_not_nil issue
1686 1686 assert_equal 2, issue.author_id
1687 1687 assert_equal 3, issue.tracker_id
1688 1688 assert_equal 2, issue.status_id
1689 1689 assert_equal Date.parse('2010-11-07'), issue.start_date
1690 1690 assert_nil issue.estimated_hours
1691 1691 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
1692 1692 assert_not_nil v
1693 1693 assert_equal 'Value for field 2', v.value
1694 1694 end
1695 1695
1696 1696 def test_post_new_with_group_assignment
1697 1697 group = Group.find(11)
1698 1698 project = Project.find(1)
1699 1699 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
1700 1700
1701 1701 with_settings :issue_group_assignment => '1' do
1702 1702 @request.session[:user_id] = 2
1703 1703 assert_difference 'Issue.count' do
1704 1704 post :create, :project_id => project.id,
1705 1705 :issue => {:tracker_id => 3,
1706 1706 :status_id => 1,
1707 1707 :subject => 'This is the test_new_with_group_assignment issue',
1708 1708 :assigned_to_id => group.id}
1709 1709 end
1710 1710 end
1711 1711 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1712 1712
1713 1713 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
1714 1714 assert_not_nil issue
1715 1715 assert_equal group, issue.assigned_to
1716 1716 end
1717 1717
1718 1718 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
1719 1719 Setting.default_issue_start_date_to_creation_date = 0
1720 1720
1721 1721 @request.session[:user_id] = 2
1722 1722 assert_difference 'Issue.count' do
1723 1723 post :create, :project_id => 1,
1724 1724 :issue => {:tracker_id => 3,
1725 1725 :status_id => 2,
1726 1726 :subject => 'This is the test_new issue',
1727 1727 :description => 'This is the description',
1728 1728 :priority_id => 5,
1729 1729 :estimated_hours => '',
1730 1730 :custom_field_values => {'2' => 'Value for field 2'}}
1731 1731 end
1732 1732 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1733 1733
1734 1734 issue = Issue.find_by_subject('This is the test_new issue')
1735 1735 assert_not_nil issue
1736 1736 assert_nil issue.start_date
1737 1737 end
1738 1738
1739 1739 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1740 1740 Setting.default_issue_start_date_to_creation_date = 1
1741 1741
1742 1742 @request.session[:user_id] = 2
1743 1743 assert_difference 'Issue.count' do
1744 1744 post :create, :project_id => 1,
1745 1745 :issue => {:tracker_id => 3,
1746 1746 :status_id => 2,
1747 1747 :subject => 'This is the test_new issue',
1748 1748 :description => 'This is the description',
1749 1749 :priority_id => 5,
1750 1750 :estimated_hours => '',
1751 1751 :custom_field_values => {'2' => 'Value for field 2'}}
1752 1752 end
1753 1753 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1754 1754
1755 1755 issue = Issue.find_by_subject('This is the test_new issue')
1756 1756 assert_not_nil issue
1757 1757 assert_equal Date.today, issue.start_date
1758 1758 end
1759 1759
1760 1760 def test_post_create_and_continue
1761 1761 @request.session[:user_id] = 2
1762 1762 assert_difference 'Issue.count' do
1763 1763 post :create, :project_id => 1,
1764 1764 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1765 1765 :continue => ''
1766 1766 end
1767 1767
1768 1768 issue = Issue.first(:order => 'id DESC')
1769 1769 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1770 1770 assert_not_nil flash[:notice], "flash was not set"
1771 1771 assert_include %|<a href="/issues/#{issue.id}" title="This is first issue">##{issue.id}</a>|, flash[:notice], "issue link not found in the flash message"
1772 1772 end
1773 1773
1774 1774 def test_post_create_without_custom_fields_param
1775 1775 @request.session[:user_id] = 2
1776 1776 assert_difference 'Issue.count' do
1777 1777 post :create, :project_id => 1,
1778 1778 :issue => {:tracker_id => 1,
1779 1779 :subject => 'This is the test_new issue',
1780 1780 :description => 'This is the description',
1781 1781 :priority_id => 5}
1782 1782 end
1783 1783 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1784 1784 end
1785 1785
1786 1786 def test_post_create_with_multi_custom_field
1787 1787 field = IssueCustomField.find_by_name('Database')
1788 1788 field.update_attribute(:multiple, true)
1789 1789
1790 1790 @request.session[:user_id] = 2
1791 1791 assert_difference 'Issue.count' do
1792 1792 post :create, :project_id => 1,
1793 1793 :issue => {:tracker_id => 1,
1794 1794 :subject => 'This is the test_new issue',
1795 1795 :description => 'This is the description',
1796 1796 :priority_id => 5,
1797 1797 :custom_field_values => {'1' => ['', 'MySQL', 'Oracle']}}
1798 1798 end
1799 1799 assert_response 302
1800 1800 issue = Issue.first(:order => 'id DESC')
1801 1801 assert_equal ['MySQL', 'Oracle'], issue.custom_field_value(1).sort
1802 1802 end
1803 1803
1804 1804 def test_post_create_with_empty_multi_custom_field
1805 1805 field = IssueCustomField.find_by_name('Database')
1806 1806 field.update_attribute(:multiple, true)
1807 1807
1808 1808 @request.session[:user_id] = 2
1809 1809 assert_difference 'Issue.count' do
1810 1810 post :create, :project_id => 1,
1811 1811 :issue => {:tracker_id => 1,
1812 1812 :subject => 'This is the test_new issue',
1813 1813 :description => 'This is the description',
1814 1814 :priority_id => 5,
1815 1815 :custom_field_values => {'1' => ['']}}
1816 1816 end
1817 1817 assert_response 302
1818 1818 issue = Issue.first(:order => 'id DESC')
1819 1819 assert_equal [''], issue.custom_field_value(1).sort
1820 1820 end
1821 1821
1822 1822 def test_post_create_with_multi_user_custom_field
1823 1823 field = IssueCustomField.create!(:name => 'Multi user', :field_format => 'user', :multiple => true,
1824 1824 :tracker_ids => [1], :is_for_all => true)
1825 1825
1826 1826 @request.session[:user_id] = 2
1827 1827 assert_difference 'Issue.count' do
1828 1828 post :create, :project_id => 1,
1829 1829 :issue => {:tracker_id => 1,
1830 1830 :subject => 'This is the test_new issue',
1831 1831 :description => 'This is the description',
1832 1832 :priority_id => 5,
1833 1833 :custom_field_values => {field.id.to_s => ['', '2', '3']}}
1834 1834 end
1835 1835 assert_response 302
1836 1836 issue = Issue.first(:order => 'id DESC')
1837 1837 assert_equal ['2', '3'], issue.custom_field_value(field).sort
1838 1838 end
1839 1839
1840 1840 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1841 1841 field = IssueCustomField.find_by_name('Database')
1842 1842 field.update_attribute(:is_required, true)
1843 1843
1844 1844 @request.session[:user_id] = 2
1845 1845 assert_no_difference 'Issue.count' do
1846 1846 post :create, :project_id => 1,
1847 1847 :issue => {:tracker_id => 1,
1848 1848 :subject => 'This is the test_new issue',
1849 1849 :description => 'This is the description',
1850 1850 :priority_id => 5}
1851 1851 end
1852 1852 assert_response :success
1853 1853 assert_template 'new'
1854 1854 issue = assigns(:issue)
1855 1855 assert_not_nil issue
1856 1856 assert_error_tag :content => /Database can&#x27;t be blank/
1857 1857 end
1858 1858
1859 1859 def test_create_should_validate_required_fields
1860 1860 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1861 1861 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1862 1862 WorkflowPermission.delete_all
1863 1863 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'required')
1864 1864 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'required')
1865 1865 @request.session[:user_id] = 2
1866 1866
1867 1867 assert_no_difference 'Issue.count' do
1868 1868 post :create, :project_id => 1, :issue => {
1869 1869 :tracker_id => 2,
1870 1870 :status_id => 1,
1871 1871 :subject => 'Test',
1872 1872 :start_date => '',
1873 1873 :due_date => '',
1874 1874 :custom_field_values => {cf1.id.to_s => '', cf2.id.to_s => ''}
1875 1875 }
1876 1876 assert_response :success
1877 1877 assert_template 'new'
1878 1878 end
1879 1879
1880 1880 assert_error_tag :content => /Due date can&#x27;t be blank/i
1881 1881 assert_error_tag :content => /Bar can&#x27;t be blank/i
1882 1882 end
1883 1883
1884 1884 def test_create_should_ignore_readonly_fields
1885 1885 cf1 = IssueCustomField.create!(:name => 'Foo', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1886 1886 cf2 = IssueCustomField.create!(:name => 'Bar', :field_format => 'string', :is_for_all => true, :tracker_ids => [1, 2])
1887 1887 WorkflowPermission.delete_all
1888 1888 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => 'due_date', :rule => 'readonly')
1889 1889 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2, :role_id => 1, :field_name => cf2.id.to_s, :rule => 'readonly')
1890 1890 @request.session[:user_id] = 2
1891 1891
1892 1892 assert_difference 'Issue.count' do
1893 1893 post :create, :project_id => 1, :issue => {
1894 1894 :tracker_id => 2,
1895 1895 :status_id => 1,
1896 1896 :subject => 'Test',
1897 1897 :start_date => '2012-07-14',
1898 1898 :due_date => '2012-07-16',
1899 1899 :custom_field_values => {cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'}
1900 1900 }
1901 1901 assert_response 302
1902 1902 end
1903 1903
1904 1904 issue = Issue.first(:order => 'id DESC')
1905 1905 assert_equal Date.parse('2012-07-14'), issue.start_date
1906 1906 assert_nil issue.due_date
1907 1907 assert_equal 'value1', issue.custom_field_value(cf1)
1908 1908 assert_nil issue.custom_field_value(cf2)
1909 1909 end
1910 1910
1911 1911 def test_post_create_with_watchers
1912 1912 @request.session[:user_id] = 2
1913 1913 ActionMailer::Base.deliveries.clear
1914 1914
1915 1915 assert_difference 'Watcher.count', 2 do
1916 1916 post :create, :project_id => 1,
1917 1917 :issue => {:tracker_id => 1,
1918 1918 :subject => 'This is a new issue with watchers',
1919 1919 :description => 'This is the description',
1920 1920 :priority_id => 5,
1921 1921 :watcher_user_ids => ['2', '3']}
1922 1922 end
1923 1923 issue = Issue.find_by_subject('This is a new issue with watchers')
1924 1924 assert_not_nil issue
1925 1925 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1926 1926
1927 1927 # Watchers added
1928 1928 assert_equal [2, 3], issue.watcher_user_ids.sort
1929 1929 assert issue.watched_by?(User.find(3))
1930 1930 # Watchers notified
1931 1931 mail = ActionMailer::Base.deliveries.last
1932 1932 assert_not_nil mail
1933 1933 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1934 1934 end
1935 1935
1936 1936 def test_post_create_subissue
1937 1937 @request.session[:user_id] = 2
1938 1938
1939 1939 assert_difference 'Issue.count' do
1940 1940 post :create, :project_id => 1,
1941 1941 :issue => {:tracker_id => 1,
1942 1942 :subject => 'This is a child issue',
1943 :parent_issue_id => 2}
1943 :parent_issue_id => '2'}
1944 assert_response 302
1945 end
1946 issue = Issue.order('id DESC').first
1947 assert_equal Issue.find(2), issue.parent
1948 end
1949
1950 def test_post_create_subissue_with_sharp_parent_id
1951 @request.session[:user_id] = 2
1944 1952
1953 assert_difference 'Issue.count' do
1954 post :create, :project_id => 1,
1955 :issue => {:tracker_id => 1,
1956 :subject => 'This is a child issue',
1957 :parent_issue_id => '#2'}
1945 1958 assert_response 302
1946 1959 end
1947 issue = Issue.find_by_subject('This is a child issue')
1948 assert_not_nil issue
1960 issue = Issue.order('id DESC').first
1949 1961 assert_equal Issue.find(2), issue.parent
1950 1962 end
1951 1963
1952 1964 def test_post_create_subissue_with_non_visible_parent_id_should_not_validate
1953 1965 @request.session[:user_id] = 2
1954 1966
1955 1967 assert_no_difference 'Issue.count' do
1956 1968 post :create, :project_id => 1,
1957 1969 :issue => {:tracker_id => 1,
1958 1970 :subject => 'This is a child issue',
1959 1971 :parent_issue_id => '4'}
1960 1972
1961 1973 assert_response :success
1962 1974 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '4'
1963 1975 assert_error_tag :content => /Parent task is invalid/i
1964 1976 end
1965 1977 end
1966 1978
1967 1979 def test_post_create_subissue_with_non_numeric_parent_id_should_not_validate
1968 1980 @request.session[:user_id] = 2
1969 1981
1970 1982 assert_no_difference 'Issue.count' do
1971 1983 post :create, :project_id => 1,
1972 1984 :issue => {:tracker_id => 1,
1973 1985 :subject => 'This is a child issue',
1974 1986 :parent_issue_id => '01ABC'}
1975 1987
1976 1988 assert_response :success
1977 1989 assert_select 'input[name=?][value=?]', 'issue[parent_issue_id]', '01ABC'
1978 1990 assert_error_tag :content => /Parent task is invalid/i
1979 1991 end
1980 1992 end
1981 1993
1982 1994 def test_post_create_private
1983 1995 @request.session[:user_id] = 2
1984 1996
1985 1997 assert_difference 'Issue.count' do
1986 1998 post :create, :project_id => 1,
1987 1999 :issue => {:tracker_id => 1,
1988 2000 :subject => 'This is a private issue',
1989 2001 :is_private => '1'}
1990 2002 end
1991 2003 issue = Issue.first(:order => 'id DESC')
1992 2004 assert issue.is_private?
1993 2005 end
1994 2006
1995 2007 def test_post_create_private_with_set_own_issues_private_permission
1996 2008 role = Role.find(1)
1997 2009 role.remove_permission! :set_issues_private
1998 2010 role.add_permission! :set_own_issues_private
1999 2011
2000 2012 @request.session[:user_id] = 2
2001 2013
2002 2014 assert_difference 'Issue.count' do
2003 2015 post :create, :project_id => 1,
2004 2016 :issue => {:tracker_id => 1,
2005 2017 :subject => 'This is a private issue',
2006 2018 :is_private => '1'}
2007 2019 end
2008 2020 issue = Issue.first(:order => 'id DESC')
2009 2021 assert issue.is_private?
2010 2022 end
2011 2023
2012 2024 def test_post_create_should_send_a_notification
2013 2025 ActionMailer::Base.deliveries.clear
2014 2026 @request.session[:user_id] = 2
2015 2027 assert_difference 'Issue.count' do
2016 2028 post :create, :project_id => 1,
2017 2029 :issue => {:tracker_id => 3,
2018 2030 :subject => 'This is the test_new issue',
2019 2031 :description => 'This is the description',
2020 2032 :priority_id => 5,
2021 2033 :estimated_hours => '',
2022 2034 :custom_field_values => {'2' => 'Value for field 2'}}
2023 2035 end
2024 2036 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
2025 2037
2026 2038 assert_equal 1, ActionMailer::Base.deliveries.size
2027 2039 end
2028 2040
2029 2041 def test_post_create_should_preserve_fields_values_on_validation_failure
2030 2042 @request.session[:user_id] = 2
2031 2043 post :create, :project_id => 1,
2032 2044 :issue => {:tracker_id => 1,
2033 2045 # empty subject
2034 2046 :subject => '',
2035 2047 :description => 'This is a description',
2036 2048 :priority_id => 6,
2037 2049 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
2038 2050 assert_response :success
2039 2051 assert_template 'new'
2040 2052
2041 2053 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
2042 2054 :content => "\nThis is a description"
2043 2055 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
2044 2056 :child => { :tag => 'option', :attributes => { :selected => 'selected',
2045 2057 :value => '6' },
2046 2058 :content => 'High' }
2047 2059 # Custom fields
2048 2060 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
2049 2061 :child => { :tag => 'option', :attributes => { :selected => 'selected',
2050 2062 :value => 'Oracle' },
2051 2063 :content => 'Oracle' }
2052 2064 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
2053 2065 :value => 'Value for field 2'}
2054 2066 end
2055 2067
2056 2068 def test_post_create_with_failure_should_preserve_watchers
2057 2069 assert !User.find(8).member_of?(Project.find(1))
2058 2070
2059 2071 @request.session[:user_id] = 2
2060 2072 post :create, :project_id => 1,
2061 2073 :issue => {:tracker_id => 1,
2062 2074 :watcher_user_ids => ['3', '8']}
2063 2075 assert_response :success
2064 2076 assert_template 'new'
2065 2077
2066 2078 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '2', :checked => nil}
2067 2079 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '3', :checked => 'checked'}
2068 2080 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]', :value => '8', :checked => 'checked'}
2069 2081 end
2070 2082
2071 2083 def test_post_create_should_ignore_non_safe_attributes
2072 2084 @request.session[:user_id] = 2
2073 2085 assert_nothing_raised do
2074 2086 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
2075 2087 end
2076 2088 end
2077 2089
2078 2090 def test_post_create_with_attachment
2079 2091 set_tmp_attachments_directory
2080 2092 @request.session[:user_id] = 2
2081 2093
2082 2094 assert_difference 'Issue.count' do
2083 2095 assert_difference 'Attachment.count' do
2084 2096 post :create, :project_id => 1,
2085 2097 :issue => { :tracker_id => '1', :subject => 'With attachment' },
2086 2098 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2087 2099 end
2088 2100 end
2089 2101
2090 2102 issue = Issue.first(:order => 'id DESC')
2091 2103 attachment = Attachment.first(:order => 'id DESC')
2092 2104
2093 2105 assert_equal issue, attachment.container
2094 2106 assert_equal 2, attachment.author_id
2095 2107 assert_equal 'testfile.txt', attachment.filename
2096 2108 assert_equal 'text/plain', attachment.content_type
2097 2109 assert_equal 'test file', attachment.description
2098 2110 assert_equal 59, attachment.filesize
2099 2111 assert File.exists?(attachment.diskfile)
2100 2112 assert_equal 59, File.size(attachment.diskfile)
2101 2113 end
2102 2114
2103 2115 def test_post_create_with_failure_should_save_attachments
2104 2116 set_tmp_attachments_directory
2105 2117 @request.session[:user_id] = 2
2106 2118
2107 2119 assert_no_difference 'Issue.count' do
2108 2120 assert_difference 'Attachment.count' do
2109 2121 post :create, :project_id => 1,
2110 2122 :issue => { :tracker_id => '1', :subject => '' },
2111 2123 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2112 2124 assert_response :success
2113 2125 assert_template 'new'
2114 2126 end
2115 2127 end
2116 2128
2117 2129 attachment = Attachment.first(:order => 'id DESC')
2118 2130 assert_equal 'testfile.txt', attachment.filename
2119 2131 assert File.exists?(attachment.diskfile)
2120 2132 assert_nil attachment.container
2121 2133
2122 2134 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2123 2135 assert_tag 'span', :content => /testfile.txt/
2124 2136 end
2125 2137
2126 2138 def test_post_create_with_failure_should_keep_saved_attachments
2127 2139 set_tmp_attachments_directory
2128 2140 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2129 2141 @request.session[:user_id] = 2
2130 2142
2131 2143 assert_no_difference 'Issue.count' do
2132 2144 assert_no_difference 'Attachment.count' do
2133 2145 post :create, :project_id => 1,
2134 2146 :issue => { :tracker_id => '1', :subject => '' },
2135 2147 :attachments => {'p0' => {'token' => attachment.token}}
2136 2148 assert_response :success
2137 2149 assert_template 'new'
2138 2150 end
2139 2151 end
2140 2152
2141 2153 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2142 2154 assert_tag 'span', :content => /testfile.txt/
2143 2155 end
2144 2156
2145 2157 def test_post_create_should_attach_saved_attachments
2146 2158 set_tmp_attachments_directory
2147 2159 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2148 2160 @request.session[:user_id] = 2
2149 2161
2150 2162 assert_difference 'Issue.count' do
2151 2163 assert_no_difference 'Attachment.count' do
2152 2164 post :create, :project_id => 1,
2153 2165 :issue => { :tracker_id => '1', :subject => 'Saved attachments' },
2154 2166 :attachments => {'p0' => {'token' => attachment.token}}
2155 2167 assert_response 302
2156 2168 end
2157 2169 end
2158 2170
2159 2171 issue = Issue.first(:order => 'id DESC')
2160 2172 assert_equal 1, issue.attachments.count
2161 2173
2162 2174 attachment.reload
2163 2175 assert_equal issue, attachment.container
2164 2176 end
2165 2177
2166 2178 context "without workflow privilege" do
2167 2179 setup do
2168 2180 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2169 2181 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2170 2182 end
2171 2183
2172 2184 context "#new" do
2173 2185 should "propose default status only" do
2174 2186 get :new, :project_id => 1
2175 2187 assert_response :success
2176 2188 assert_template 'new'
2177 2189 assert_tag :tag => 'select',
2178 2190 :attributes => {:name => 'issue[status_id]'},
2179 2191 :children => {:count => 1},
2180 2192 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
2181 2193 end
2182 2194
2183 2195 should "accept default status" do
2184 2196 assert_difference 'Issue.count' do
2185 2197 post :create, :project_id => 1,
2186 2198 :issue => {:tracker_id => 1,
2187 2199 :subject => 'This is an issue',
2188 2200 :status_id => 1}
2189 2201 end
2190 2202 issue = Issue.last(:order => 'id')
2191 2203 assert_equal IssueStatus.default, issue.status
2192 2204 end
2193 2205
2194 2206 should "ignore unauthorized status" do
2195 2207 assert_difference 'Issue.count' do
2196 2208 post :create, :project_id => 1,
2197 2209 :issue => {:tracker_id => 1,
2198 2210 :subject => 'This is an issue',
2199 2211 :status_id => 3}
2200 2212 end
2201 2213 issue = Issue.last(:order => 'id')
2202 2214 assert_equal IssueStatus.default, issue.status
2203 2215 end
2204 2216 end
2205 2217
2206 2218 context "#update" do
2207 2219 should "ignore status change" do
2208 2220 assert_difference 'Journal.count' do
2209 2221 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2210 2222 end
2211 2223 assert_equal 1, Issue.find(1).status_id
2212 2224 end
2213 2225
2214 2226 should "ignore attributes changes" do
2215 2227 assert_difference 'Journal.count' do
2216 2228 put :update, :id => 1, :issue => {:subject => 'changed', :assigned_to_id => 2, :notes => 'just trying'}
2217 2229 end
2218 2230 issue = Issue.find(1)
2219 2231 assert_equal "Can't print recipes", issue.subject
2220 2232 assert_nil issue.assigned_to
2221 2233 end
2222 2234 end
2223 2235 end
2224 2236
2225 2237 context "with workflow privilege" do
2226 2238 setup do
2227 2239 WorkflowTransition.delete_all(["role_id = ?", Role.anonymous.id])
2228 2240 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
2229 2241 WorkflowTransition.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
2230 2242 Role.anonymous.add_permission! :add_issues, :add_issue_notes
2231 2243 end
2232 2244
2233 2245 context "#update" do
2234 2246 should "accept authorized status" do
2235 2247 assert_difference 'Journal.count' do
2236 2248 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2237 2249 end
2238 2250 assert_equal 3, Issue.find(1).status_id
2239 2251 end
2240 2252
2241 2253 should "ignore unauthorized status" do
2242 2254 assert_difference 'Journal.count' do
2243 2255 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2244 2256 end
2245 2257 assert_equal 1, Issue.find(1).status_id
2246 2258 end
2247 2259
2248 2260 should "accept authorized attributes changes" do
2249 2261 assert_difference 'Journal.count' do
2250 2262 put :update, :id => 1, :issue => {:assigned_to_id => 2, :notes => 'just trying'}
2251 2263 end
2252 2264 issue = Issue.find(1)
2253 2265 assert_equal 2, issue.assigned_to_id
2254 2266 end
2255 2267
2256 2268 should "ignore unauthorized attributes changes" do
2257 2269 assert_difference 'Journal.count' do
2258 2270 put :update, :id => 1, :issue => {:subject => 'changed', :notes => 'just trying'}
2259 2271 end
2260 2272 issue = Issue.find(1)
2261 2273 assert_equal "Can't print recipes", issue.subject
2262 2274 end
2263 2275 end
2264 2276
2265 2277 context "and :edit_issues permission" do
2266 2278 setup do
2267 2279 Role.anonymous.add_permission! :add_issues, :edit_issues
2268 2280 end
2269 2281
2270 2282 should "accept authorized status" do
2271 2283 assert_difference 'Journal.count' do
2272 2284 put :update, :id => 1, :issue => {:status_id => 3, :notes => 'just trying'}
2273 2285 end
2274 2286 assert_equal 3, Issue.find(1).status_id
2275 2287 end
2276 2288
2277 2289 should "ignore unauthorized status" do
2278 2290 assert_difference 'Journal.count' do
2279 2291 put :update, :id => 1, :issue => {:status_id => 2, :notes => 'just trying'}
2280 2292 end
2281 2293 assert_equal 1, Issue.find(1).status_id
2282 2294 end
2283 2295
2284 2296 should "accept authorized attributes changes" do
2285 2297 assert_difference 'Journal.count' do
2286 2298 put :update, :id => 1, :issue => {:subject => 'changed', :assigned_to_id => 2, :notes => 'just trying'}
2287 2299 end
2288 2300 issue = Issue.find(1)
2289 2301 assert_equal "changed", issue.subject
2290 2302 assert_equal 2, issue.assigned_to_id
2291 2303 end
2292 2304 end
2293 2305 end
2294 2306
2295 2307 def test_new_as_copy
2296 2308 @request.session[:user_id] = 2
2297 2309 get :new, :project_id => 1, :copy_from => 1
2298 2310
2299 2311 assert_response :success
2300 2312 assert_template 'new'
2301 2313
2302 2314 assert_not_nil assigns(:issue)
2303 2315 orig = Issue.find(1)
2304 2316 assert_equal 1, assigns(:issue).project_id
2305 2317 assert_equal orig.subject, assigns(:issue).subject
2306 2318 assert assigns(:issue).copy?
2307 2319
2308 2320 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
2309 2321 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
2310 2322 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2311 2323 :child => {:tag => 'option', :attributes => {:value => '1', :selected => 'selected'}, :content => 'eCookbook'}
2312 2324 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2313 2325 :child => {:tag => 'option', :attributes => {:value => '2', :selected => nil}, :content => 'OnlineStore'}
2314 2326 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
2315 2327 end
2316 2328
2317 2329 def test_new_as_copy_with_attachments_should_show_copy_attachments_checkbox
2318 2330 @request.session[:user_id] = 2
2319 2331 issue = Issue.find(3)
2320 2332 assert issue.attachments.count > 0
2321 2333 get :new, :project_id => 1, :copy_from => 3
2322 2334
2323 2335 assert_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
2324 2336 end
2325 2337
2326 2338 def test_new_as_copy_without_attachments_should_not_show_copy_attachments_checkbox
2327 2339 @request.session[:user_id] = 2
2328 2340 issue = Issue.find(3)
2329 2341 issue.attachments.delete_all
2330 2342 get :new, :project_id => 1, :copy_from => 3
2331 2343
2332 2344 assert_no_tag 'input', :attributes => {:name => 'copy_attachments', :type => 'checkbox', :checked => 'checked', :value => '1'}
2333 2345 end
2334 2346
2335 2347 def test_new_as_copy_with_subtasks_should_show_copy_subtasks_checkbox
2336 2348 @request.session[:user_id] = 2
2337 2349 issue = Issue.generate_with_descendants!
2338 2350 get :new, :project_id => 1, :copy_from => issue.id
2339 2351
2340 2352 assert_select 'input[type=checkbox][name=copy_subtasks][checked=checked][value=1]'
2341 2353 end
2342 2354
2343 2355 def test_new_as_copy_with_invalid_issue_should_respond_with_404
2344 2356 @request.session[:user_id] = 2
2345 2357 get :new, :project_id => 1, :copy_from => 99999
2346 2358 assert_response 404
2347 2359 end
2348 2360
2349 2361 def test_create_as_copy_on_different_project
2350 2362 @request.session[:user_id] = 2
2351 2363 assert_difference 'Issue.count' do
2352 2364 post :create, :project_id => 1, :copy_from => 1,
2353 2365 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2354 2366
2355 2367 assert_not_nil assigns(:issue)
2356 2368 assert assigns(:issue).copy?
2357 2369 end
2358 2370 issue = Issue.first(:order => 'id DESC')
2359 2371 assert_redirected_to "/issues/#{issue.id}"
2360 2372
2361 2373 assert_equal 2, issue.project_id
2362 2374 assert_equal 3, issue.tracker_id
2363 2375 assert_equal 'Copy', issue.subject
2364 2376 end
2365 2377
2366 2378 def test_create_as_copy_should_copy_attachments
2367 2379 @request.session[:user_id] = 2
2368 2380 issue = Issue.find(3)
2369 2381 count = issue.attachments.count
2370 2382 assert count > 0
2371 2383
2372 2384 assert_difference 'Issue.count' do
2373 2385 assert_difference 'Attachment.count', count do
2374 2386 assert_no_difference 'Journal.count' do
2375 2387 post :create, :project_id => 1, :copy_from => 3,
2376 2388 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2377 2389 :copy_attachments => '1'
2378 2390 end
2379 2391 end
2380 2392 end
2381 2393 copy = Issue.first(:order => 'id DESC')
2382 2394 assert_equal count, copy.attachments.count
2383 2395 assert_equal issue.attachments.map(&:filename).sort, copy.attachments.map(&:filename).sort
2384 2396 end
2385 2397
2386 2398 def test_create_as_copy_without_copy_attachments_option_should_not_copy_attachments
2387 2399 @request.session[:user_id] = 2
2388 2400 issue = Issue.find(3)
2389 2401 count = issue.attachments.count
2390 2402 assert count > 0
2391 2403
2392 2404 assert_difference 'Issue.count' do
2393 2405 assert_no_difference 'Attachment.count' do
2394 2406 assert_no_difference 'Journal.count' do
2395 2407 post :create, :project_id => 1, :copy_from => 3,
2396 2408 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'}
2397 2409 end
2398 2410 end
2399 2411 end
2400 2412 copy = Issue.first(:order => 'id DESC')
2401 2413 assert_equal 0, copy.attachments.count
2402 2414 end
2403 2415
2404 2416 def test_create_as_copy_with_attachments_should_add_new_files
2405 2417 @request.session[:user_id] = 2
2406 2418 issue = Issue.find(3)
2407 2419 count = issue.attachments.count
2408 2420 assert count > 0
2409 2421
2410 2422 assert_difference 'Issue.count' do
2411 2423 assert_difference 'Attachment.count', count + 1 do
2412 2424 assert_no_difference 'Journal.count' do
2413 2425 post :create, :project_id => 1, :copy_from => 3,
2414 2426 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with attachments'},
2415 2427 :copy_attachments => '1',
2416 2428 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2417 2429 end
2418 2430 end
2419 2431 end
2420 2432 copy = Issue.first(:order => 'id DESC')
2421 2433 assert_equal count + 1, copy.attachments.count
2422 2434 end
2423 2435
2424 2436 def test_create_as_copy_should_add_relation_with_copied_issue
2425 2437 @request.session[:user_id] = 2
2426 2438
2427 2439 assert_difference 'Issue.count' do
2428 2440 assert_difference 'IssueRelation.count' do
2429 2441 post :create, :project_id => 1, :copy_from => 1,
2430 2442 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2431 2443 end
2432 2444 end
2433 2445 copy = Issue.first(:order => 'id DESC')
2434 2446 assert_equal 1, copy.relations.size
2435 2447 end
2436 2448
2437 2449 def test_create_as_copy_should_copy_subtasks
2438 2450 @request.session[:user_id] = 2
2439 2451 issue = Issue.generate_with_descendants!
2440 2452 count = issue.descendants.count
2441 2453
2442 2454 assert_difference 'Issue.count', count+1 do
2443 2455 assert_no_difference 'Journal.count' do
2444 2456 post :create, :project_id => 1, :copy_from => issue.id,
2445 2457 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'},
2446 2458 :copy_subtasks => '1'
2447 2459 end
2448 2460 end
2449 2461 copy = Issue.where(:parent_id => nil).first(:order => 'id DESC')
2450 2462 assert_equal count, copy.descendants.count
2451 2463 assert_equal issue.descendants.map(&:subject).sort, copy.descendants.map(&:subject).sort
2452 2464 end
2453 2465
2454 2466 def test_create_as_copy_without_copy_subtasks_option_should_not_copy_subtasks
2455 2467 @request.session[:user_id] = 2
2456 2468 issue = Issue.generate_with_descendants!
2457 2469
2458 2470 assert_difference 'Issue.count', 1 do
2459 2471 assert_no_difference 'Journal.count' do
2460 2472 post :create, :project_id => 1, :copy_from => 3,
2461 2473 :issue => {:project_id => '1', :tracker_id => '3', :status_id => '1', :subject => 'Copy with subtasks'}
2462 2474 end
2463 2475 end
2464 2476 copy = Issue.where(:parent_id => nil).first(:order => 'id DESC')
2465 2477 assert_equal 0, copy.descendants.count
2466 2478 end
2467 2479
2468 2480 def test_create_as_copy_with_failure
2469 2481 @request.session[:user_id] = 2
2470 2482 post :create, :project_id => 1, :copy_from => 1,
2471 2483 :issue => {:project_id => '2', :tracker_id => '3', :status_id => '1', :subject => ''}
2472 2484
2473 2485 assert_response :success
2474 2486 assert_template 'new'
2475 2487
2476 2488 assert_not_nil assigns(:issue)
2477 2489 assert assigns(:issue).copy?
2478 2490
2479 2491 assert_tag 'form', :attributes => {:id => 'issue-form', :action => '/projects/ecookbook/issues'}
2480 2492 assert_tag 'select', :attributes => {:name => 'issue[project_id]'}
2481 2493 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2482 2494 :child => {:tag => 'option', :attributes => {:value => '1', :selected => nil}, :content => 'eCookbook'}
2483 2495 assert_tag 'select', :attributes => {:name => 'issue[project_id]'},
2484 2496 :child => {:tag => 'option', :attributes => {:value => '2', :selected => 'selected'}, :content => 'OnlineStore'}
2485 2497 assert_tag 'input', :attributes => {:name => 'copy_from', :value => '1'}
2486 2498 end
2487 2499
2488 2500 def test_create_as_copy_on_project_without_permission_should_ignore_target_project
2489 2501 @request.session[:user_id] = 2
2490 2502 assert !User.find(2).member_of?(Project.find(4))
2491 2503
2492 2504 assert_difference 'Issue.count' do
2493 2505 post :create, :project_id => 1, :copy_from => 1,
2494 2506 :issue => {:project_id => '4', :tracker_id => '3', :status_id => '1', :subject => 'Copy'}
2495 2507 end
2496 2508 issue = Issue.first(:order => 'id DESC')
2497 2509 assert_equal 1, issue.project_id
2498 2510 end
2499 2511
2500 2512 def test_get_edit
2501 2513 @request.session[:user_id] = 2
2502 2514 get :edit, :id => 1
2503 2515 assert_response :success
2504 2516 assert_template 'edit'
2505 2517 assert_not_nil assigns(:issue)
2506 2518 assert_equal Issue.find(1), assigns(:issue)
2507 2519
2508 2520 # Be sure we don't display inactive IssuePriorities
2509 2521 assert ! IssuePriority.find(15).active?
2510 2522 assert_no_tag :option, :attributes => {:value => '15'},
2511 2523 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
2512 2524 end
2513 2525
2514 2526 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
2515 2527 @request.session[:user_id] = 2
2516 2528 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
2517 2529
2518 2530 get :edit, :id => 1
2519 2531 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2520 2532 end
2521 2533
2522 2534 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
2523 2535 @request.session[:user_id] = 2
2524 2536 Role.find_by_name('Manager').remove_permission! :log_time
2525 2537
2526 2538 get :edit, :id => 1
2527 2539 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
2528 2540 end
2529 2541
2530 2542 def test_get_edit_with_params
2531 2543 @request.session[:user_id] = 2
2532 2544 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
2533 2545 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
2534 2546 assert_response :success
2535 2547 assert_template 'edit'
2536 2548
2537 2549 issue = assigns(:issue)
2538 2550 assert_not_nil issue
2539 2551
2540 2552 assert_equal 5, issue.status_id
2541 2553 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
2542 2554 :child => { :tag => 'option',
2543 2555 :content => 'Closed',
2544 2556 :attributes => { :selected => 'selected' } }
2545 2557
2546 2558 assert_equal 7, issue.priority_id
2547 2559 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
2548 2560 :child => { :tag => 'option',
2549 2561 :content => 'Urgent',
2550 2562 :attributes => { :selected => 'selected' } }
2551 2563
2552 2564 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
2553 2565 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
2554 2566 :child => { :tag => 'option',
2555 2567 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
2556 2568 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
2557 2569 end
2558 2570
2559 2571 def test_get_edit_with_multi_custom_field
2560 2572 field = CustomField.find(1)
2561 2573 field.update_attribute :multiple, true
2562 2574 issue = Issue.find(1)
2563 2575 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2564 2576 issue.save!
2565 2577
2566 2578 @request.session[:user_id] = 2
2567 2579 get :edit, :id => 1
2568 2580 assert_response :success
2569 2581 assert_template 'edit'
2570 2582
2571 2583 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]', :multiple => 'multiple'}
2572 2584 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2573 2585 :child => {:tag => 'option', :attributes => {:value => 'MySQL', :selected => 'selected'}}
2574 2586 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2575 2587 :child => {:tag => 'option', :attributes => {:value => 'PostgreSQL', :selected => nil}}
2576 2588 assert_tag 'select', :attributes => {:name => 'issue[custom_field_values][1][]'},
2577 2589 :child => {:tag => 'option', :attributes => {:value => 'Oracle', :selected => 'selected'}}
2578 2590 end
2579 2591
2580 2592 def test_update_edit_form
2581 2593 @request.session[:user_id] = 2
2582 2594 xhr :put, :new, :project_id => 1,
2583 2595 :id => 1,
2584 2596 :issue => {:tracker_id => 2,
2585 2597 :subject => 'This is the test_new issue',
2586 2598 :description => 'This is the description',
2587 2599 :priority_id => 5}
2588 2600 assert_response :success
2589 2601 assert_equal 'text/javascript', response.content_type
2590 2602 assert_template 'update_form'
2591 2603 assert_template 'form'
2592 2604
2593 2605 issue = assigns(:issue)
2594 2606 assert_kind_of Issue, issue
2595 2607 assert_equal 1, issue.id
2596 2608 assert_equal 1, issue.project_id
2597 2609 assert_equal 2, issue.tracker_id
2598 2610 assert_equal 'This is the test_new issue', issue.subject
2599 2611 end
2600 2612
2601 2613 def test_update_edit_form_should_keep_issue_author
2602 2614 @request.session[:user_id] = 3
2603 2615 xhr :put, :new, :project_id => 1, :id => 1, :issue => {:subject => 'Changed'}
2604 2616 assert_response :success
2605 2617 assert_equal 'text/javascript', response.content_type
2606 2618
2607 2619 issue = assigns(:issue)
2608 2620 assert_equal User.find(2), issue.author
2609 2621 assert_equal 2, issue.author_id
2610 2622 assert_not_equal User.current, issue.author
2611 2623 end
2612 2624
2613 2625 def test_update_edit_form_should_propose_transitions_based_on_initial_status
2614 2626 @request.session[:user_id] = 2
2615 2627 WorkflowTransition.delete_all
2616 2628 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
2617 2629 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
2618 2630 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
2619 2631
2620 2632 xhr :put, :new, :project_id => 1,
2621 2633 :id => 2,
2622 2634 :issue => {:tracker_id => 2,
2623 2635 :status_id => 5,
2624 2636 :subject => 'This is an issue'}
2625 2637
2626 2638 assert_equal 5, assigns(:issue).status_id
2627 2639 assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
2628 2640 end
2629 2641
2630 2642 def test_update_edit_form_with_project_change
2631 2643 @request.session[:user_id] = 2
2632 2644 xhr :put, :new, :project_id => 1,
2633 2645 :id => 1,
2634 2646 :issue => {:project_id => 2,
2635 2647 :tracker_id => 2,
2636 2648 :subject => 'This is the test_new issue',
2637 2649 :description => 'This is the description',
2638 2650 :priority_id => 5}
2639 2651 assert_response :success
2640 2652 assert_template 'form'
2641 2653
2642 2654 issue = assigns(:issue)
2643 2655 assert_kind_of Issue, issue
2644 2656 assert_equal 1, issue.id
2645 2657 assert_equal 2, issue.project_id
2646 2658 assert_equal 2, issue.tracker_id
2647 2659 assert_equal 'This is the test_new issue', issue.subject
2648 2660 end
2649 2661
2650 2662 def test_put_update_without_custom_fields_param
2651 2663 @request.session[:user_id] = 2
2652 2664 ActionMailer::Base.deliveries.clear
2653 2665
2654 2666 issue = Issue.find(1)
2655 2667 assert_equal '125', issue.custom_value_for(2).value
2656 2668 old_subject = issue.subject
2657 2669 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
2658 2670
2659 2671 assert_difference('Journal.count') do
2660 2672 assert_difference('JournalDetail.count', 2) do
2661 2673 put :update, :id => 1, :issue => {:subject => new_subject,
2662 2674 :priority_id => '6',
2663 2675 :category_id => '1' # no change
2664 2676 }
2665 2677 end
2666 2678 end
2667 2679 assert_redirected_to :action => 'show', :id => '1'
2668 2680 issue.reload
2669 2681 assert_equal new_subject, issue.subject
2670 2682 # Make sure custom fields were not cleared
2671 2683 assert_equal '125', issue.custom_value_for(2).value
2672 2684
2673 2685 mail = ActionMailer::Base.deliveries.last
2674 2686 assert_not_nil mail
2675 2687 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2676 2688 assert_mail_body_match "Subject changed from #{old_subject} to #{new_subject}", mail
2677 2689 end
2678 2690
2679 2691 def test_put_update_with_project_change
2680 2692 @request.session[:user_id] = 2
2681 2693 ActionMailer::Base.deliveries.clear
2682 2694
2683 2695 assert_difference('Journal.count') do
2684 2696 assert_difference('JournalDetail.count', 3) do
2685 2697 put :update, :id => 1, :issue => {:project_id => '2',
2686 2698 :tracker_id => '1', # no change
2687 2699 :priority_id => '6',
2688 2700 :category_id => '3'
2689 2701 }
2690 2702 end
2691 2703 end
2692 2704 assert_redirected_to :action => 'show', :id => '1'
2693 2705 issue = Issue.find(1)
2694 2706 assert_equal 2, issue.project_id
2695 2707 assert_equal 1, issue.tracker_id
2696 2708 assert_equal 6, issue.priority_id
2697 2709 assert_equal 3, issue.category_id
2698 2710
2699 2711 mail = ActionMailer::Base.deliveries.last
2700 2712 assert_not_nil mail
2701 2713 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2702 2714 assert_mail_body_match "Project changed from eCookbook to OnlineStore", mail
2703 2715 end
2704 2716
2705 2717 def test_put_update_with_tracker_change
2706 2718 @request.session[:user_id] = 2
2707 2719 ActionMailer::Base.deliveries.clear
2708 2720
2709 2721 assert_difference('Journal.count') do
2710 2722 assert_difference('JournalDetail.count', 2) do
2711 2723 put :update, :id => 1, :issue => {:project_id => '1',
2712 2724 :tracker_id => '2',
2713 2725 :priority_id => '6'
2714 2726 }
2715 2727 end
2716 2728 end
2717 2729 assert_redirected_to :action => 'show', :id => '1'
2718 2730 issue = Issue.find(1)
2719 2731 assert_equal 1, issue.project_id
2720 2732 assert_equal 2, issue.tracker_id
2721 2733 assert_equal 6, issue.priority_id
2722 2734 assert_equal 1, issue.category_id
2723 2735
2724 2736 mail = ActionMailer::Base.deliveries.last
2725 2737 assert_not_nil mail
2726 2738 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
2727 2739 assert_mail_body_match "Tracker changed from Bug to Feature request", mail
2728 2740 end
2729 2741
2730 2742 def test_put_update_with_custom_field_change
2731 2743 @request.session[:user_id] = 2
2732 2744 issue = Issue.find(1)
2733 2745 assert_equal '125', issue.custom_value_for(2).value
2734 2746
2735 2747 assert_difference('Journal.count') do
2736 2748 assert_difference('JournalDetail.count', 3) do
2737 2749 put :update, :id => 1, :issue => {:subject => 'Custom field change',
2738 2750 :priority_id => '6',
2739 2751 :category_id => '1', # no change
2740 2752 :custom_field_values => { '2' => 'New custom value' }
2741 2753 }
2742 2754 end
2743 2755 end
2744 2756 assert_redirected_to :action => 'show', :id => '1'
2745 2757 issue.reload
2746 2758 assert_equal 'New custom value', issue.custom_value_for(2).value
2747 2759
2748 2760 mail = ActionMailer::Base.deliveries.last
2749 2761 assert_not_nil mail
2750 2762 assert_mail_body_match "Searchable field changed from 125 to New custom value", mail
2751 2763 end
2752 2764
2753 2765 def test_put_update_with_multi_custom_field_change
2754 2766 field = CustomField.find(1)
2755 2767 field.update_attribute :multiple, true
2756 2768 issue = Issue.find(1)
2757 2769 issue.custom_field_values = {1 => ['MySQL', 'Oracle']}
2758 2770 issue.save!
2759 2771
2760 2772 @request.session[:user_id] = 2
2761 2773 assert_difference('Journal.count') do
2762 2774 assert_difference('JournalDetail.count', 3) do
2763 2775 put :update, :id => 1,
2764 2776 :issue => {
2765 2777 :subject => 'Custom field change',
2766 2778 :custom_field_values => { '1' => ['', 'Oracle', 'PostgreSQL'] }
2767 2779 }
2768 2780 end
2769 2781 end
2770 2782 assert_redirected_to :action => 'show', :id => '1'
2771 2783 assert_equal ['Oracle', 'PostgreSQL'], Issue.find(1).custom_field_value(1).sort
2772 2784 end
2773 2785
2774 2786 def test_put_update_with_status_and_assignee_change
2775 2787 issue = Issue.find(1)
2776 2788 assert_equal 1, issue.status_id
2777 2789 @request.session[:user_id] = 2
2778 2790 assert_difference('TimeEntry.count', 0) do
2779 2791 put :update,
2780 2792 :id => 1,
2781 2793 :issue => { :status_id => 2, :assigned_to_id => 3, :notes => 'Assigned to dlopper' },
2782 2794 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
2783 2795 end
2784 2796 assert_redirected_to :action => 'show', :id => '1'
2785 2797 issue.reload
2786 2798 assert_equal 2, issue.status_id
2787 2799 j = Journal.find(:first, :order => 'id DESC')
2788 2800 assert_equal 'Assigned to dlopper', j.notes
2789 2801 assert_equal 2, j.details.size
2790 2802
2791 2803 mail = ActionMailer::Base.deliveries.last
2792 2804 assert_mail_body_match "Status changed from New to Assigned", mail
2793 2805 # subject should contain the new status
2794 2806 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
2795 2807 end
2796 2808
2797 2809 def test_put_update_with_note_only
2798 2810 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
2799 2811 # anonymous user
2800 2812 put :update,
2801 2813 :id => 1,
2802 2814 :issue => { :notes => notes }
2803 2815 assert_redirected_to :action => 'show', :id => '1'
2804 2816 j = Journal.find(:first, :order => 'id DESC')
2805 2817 assert_equal notes, j.notes
2806 2818 assert_equal 0, j.details.size
2807 2819 assert_equal User.anonymous, j.user
2808 2820
2809 2821 mail = ActionMailer::Base.deliveries.last
2810 2822 assert_mail_body_match notes, mail
2811 2823 end
2812 2824
2813 2825 def test_put_update_with_private_note_only
2814 2826 notes = 'Private note'
2815 2827 @request.session[:user_id] = 2
2816 2828
2817 2829 assert_difference 'Journal.count' do
2818 2830 put :update, :id => 1, :issue => {:notes => notes, :private_notes => '1'}
2819 2831 assert_redirected_to :action => 'show', :id => '1'
2820 2832 end
2821 2833
2822 2834 j = Journal.order('id DESC').first
2823 2835 assert_equal notes, j.notes
2824 2836 assert_equal true, j.private_notes
2825 2837 end
2826 2838
2827 2839 def test_put_update_with_private_note_and_changes
2828 2840 notes = 'Private note'
2829 2841 @request.session[:user_id] = 2
2830 2842
2831 2843 assert_difference 'Journal.count', 2 do
2832 2844 put :update, :id => 1, :issue => {:subject => 'New subject', :notes => notes, :private_notes => '1'}
2833 2845 assert_redirected_to :action => 'show', :id => '1'
2834 2846 end
2835 2847
2836 2848 j = Journal.order('id DESC').first
2837 2849 assert_equal notes, j.notes
2838 2850 assert_equal true, j.private_notes
2839 2851 assert_equal 0, j.details.count
2840 2852
2841 2853 j = Journal.order('id DESC').offset(1).first
2842 2854 assert_nil j.notes
2843 2855 assert_equal false, j.private_notes
2844 2856 assert_equal 1, j.details.count
2845 2857 end
2846 2858
2847 2859 def test_put_update_with_note_and_spent_time
2848 2860 @request.session[:user_id] = 2
2849 2861 spent_hours_before = Issue.find(1).spent_hours
2850 2862 assert_difference('TimeEntry.count') do
2851 2863 put :update,
2852 2864 :id => 1,
2853 2865 :issue => { :notes => '2.5 hours added' },
2854 2866 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
2855 2867 end
2856 2868 assert_redirected_to :action => 'show', :id => '1'
2857 2869
2858 2870 issue = Issue.find(1)
2859 2871
2860 2872 j = Journal.find(:first, :order => 'id DESC')
2861 2873 assert_equal '2.5 hours added', j.notes
2862 2874 assert_equal 0, j.details.size
2863 2875
2864 2876 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
2865 2877 assert_not_nil t
2866 2878 assert_equal 2.5, t.hours
2867 2879 assert_equal spent_hours_before + 2.5, issue.spent_hours
2868 2880 end
2869 2881
2870 2882 def test_put_update_with_attachment_only
2871 2883 set_tmp_attachments_directory
2872 2884
2873 2885 # Delete all fixtured journals, a race condition can occur causing the wrong
2874 2886 # journal to get fetched in the next find.
2875 2887 Journal.delete_all
2876 2888
2877 2889 # anonymous user
2878 2890 assert_difference 'Attachment.count' do
2879 2891 put :update, :id => 1,
2880 2892 :issue => {:notes => ''},
2881 2893 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2882 2894 end
2883 2895
2884 2896 assert_redirected_to :action => 'show', :id => '1'
2885 2897 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
2886 2898 assert j.notes.blank?
2887 2899 assert_equal 1, j.details.size
2888 2900 assert_equal 'testfile.txt', j.details.first.value
2889 2901 assert_equal User.anonymous, j.user
2890 2902
2891 2903 attachment = Attachment.first(:order => 'id DESC')
2892 2904 assert_equal Issue.find(1), attachment.container
2893 2905 assert_equal User.anonymous, attachment.author
2894 2906 assert_equal 'testfile.txt', attachment.filename
2895 2907 assert_equal 'text/plain', attachment.content_type
2896 2908 assert_equal 'test file', attachment.description
2897 2909 assert_equal 59, attachment.filesize
2898 2910 assert File.exists?(attachment.diskfile)
2899 2911 assert_equal 59, File.size(attachment.diskfile)
2900 2912
2901 2913 mail = ActionMailer::Base.deliveries.last
2902 2914 assert_mail_body_match 'testfile.txt', mail
2903 2915 end
2904 2916
2905 2917 def test_put_update_with_failure_should_save_attachments
2906 2918 set_tmp_attachments_directory
2907 2919 @request.session[:user_id] = 2
2908 2920
2909 2921 assert_no_difference 'Journal.count' do
2910 2922 assert_difference 'Attachment.count' do
2911 2923 put :update, :id => 1,
2912 2924 :issue => { :subject => '' },
2913 2925 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
2914 2926 assert_response :success
2915 2927 assert_template 'edit'
2916 2928 end
2917 2929 end
2918 2930
2919 2931 attachment = Attachment.first(:order => 'id DESC')
2920 2932 assert_equal 'testfile.txt', attachment.filename
2921 2933 assert File.exists?(attachment.diskfile)
2922 2934 assert_nil attachment.container
2923 2935
2924 2936 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2925 2937 assert_tag 'span', :content => /testfile.txt/
2926 2938 end
2927 2939
2928 2940 def test_put_update_with_failure_should_keep_saved_attachments
2929 2941 set_tmp_attachments_directory
2930 2942 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2931 2943 @request.session[:user_id] = 2
2932 2944
2933 2945 assert_no_difference 'Journal.count' do
2934 2946 assert_no_difference 'Attachment.count' do
2935 2947 put :update, :id => 1,
2936 2948 :issue => { :subject => '' },
2937 2949 :attachments => {'p0' => {'token' => attachment.token}}
2938 2950 assert_response :success
2939 2951 assert_template 'edit'
2940 2952 end
2941 2953 end
2942 2954
2943 2955 assert_tag 'input', :attributes => {:name => 'attachments[p0][token]', :value => attachment.token}
2944 2956 assert_tag 'span', :content => /testfile.txt/
2945 2957 end
2946 2958
2947 2959 def test_put_update_should_attach_saved_attachments
2948 2960 set_tmp_attachments_directory
2949 2961 attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", "text/plain"), :author_id => 2)
2950 2962 @request.session[:user_id] = 2
2951 2963
2952 2964 assert_difference 'Journal.count' do
2953 2965 assert_difference 'JournalDetail.count' do
2954 2966 assert_no_difference 'Attachment.count' do
2955 2967 put :update, :id => 1,
2956 2968 :issue => {:notes => 'Attachment added'},
2957 2969 :attachments => {'p0' => {'token' => attachment.token}}
2958 2970 assert_redirected_to '/issues/1'
2959 2971 end
2960 2972 end
2961 2973 end
2962 2974
2963 2975 attachment.reload
2964 2976 assert_equal Issue.find(1), attachment.container
2965 2977
2966 2978 journal = Journal.first(:order => 'id DESC')
2967 2979 assert_equal 1, journal.details.size
2968 2980 assert_equal 'testfile.txt', journal.details.first.value
2969 2981 end
2970 2982
2971 2983 def test_put_update_with_attachment_that_fails_to_save
2972 2984 set_tmp_attachments_directory
2973 2985
2974 2986 # Delete all fixtured journals, a race condition can occur causing the wrong
2975 2987 # journal to get fetched in the next find.
2976 2988 Journal.delete_all
2977 2989
2978 2990 # Mock out the unsaved attachment
2979 2991 Attachment.any_instance.stubs(:create).returns(Attachment.new)
2980 2992
2981 2993 # anonymous user
2982 2994 put :update,
2983 2995 :id => 1,
2984 2996 :issue => {:notes => ''},
2985 2997 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
2986 2998 assert_redirected_to :action => 'show', :id => '1'
2987 2999 assert_equal '1 file(s) could not be saved.', flash[:warning]
2988 3000 end
2989 3001
2990 3002 def test_put_update_with_no_change
2991 3003 issue = Issue.find(1)
2992 3004 issue.journals.clear
2993 3005 ActionMailer::Base.deliveries.clear
2994 3006
2995 3007 put :update,
2996 3008 :id => 1,
2997 3009 :issue => {:notes => ''}
2998 3010 assert_redirected_to :action => 'show', :id => '1'
2999 3011
3000 3012 issue.reload
3001 3013 assert issue.journals.empty?
3002 3014 # No email should be sent
3003 3015 assert ActionMailer::Base.deliveries.empty?
3004 3016 end
3005 3017
3006 3018 def test_put_update_should_send_a_notification
3007 3019 @request.session[:user_id] = 2
3008 3020 ActionMailer::Base.deliveries.clear
3009 3021 issue = Issue.find(1)
3010 3022 old_subject = issue.subject
3011 3023 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
3012 3024
3013 3025 put :update, :id => 1, :issue => {:subject => new_subject,
3014 3026 :priority_id => '6',
3015 3027 :category_id => '1' # no change
3016 3028 }
3017 3029 assert_equal 1, ActionMailer::Base.deliveries.size
3018 3030 end
3019 3031
3020 3032 def test_put_update_with_invalid_spent_time_hours_only
3021 3033 @request.session[:user_id] = 2
3022 3034 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3023 3035
3024 3036 assert_no_difference('Journal.count') do
3025 3037 put :update,
3026 3038 :id => 1,
3027 3039 :issue => {:notes => notes},
3028 3040 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
3029 3041 end
3030 3042 assert_response :success
3031 3043 assert_template 'edit'
3032 3044
3033 3045 assert_error_tag :descendant => {:content => /Activity can&#x27;t be blank/}
3034 3046 assert_tag :textarea, :attributes => { :name => 'issue[notes]' }, :content => "\n"+notes
3035 3047 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
3036 3048 end
3037 3049
3038 3050 def test_put_update_with_invalid_spent_time_comments_only
3039 3051 @request.session[:user_id] = 2
3040 3052 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
3041 3053
3042 3054 assert_no_difference('Journal.count') do
3043 3055 put :update,
3044 3056 :id => 1,
3045 3057 :issue => {:notes => notes},
3046 3058 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
3047 3059 end
3048 3060 assert_response :success
3049 3061 assert_template 'edit'
3050 3062
3051 3063 assert_error_tag :descendant => {:content => /Activity can&#x27;t be blank/}
3052 3064 assert_error_tag :descendant => {:content => /Hours can&#x27;t be blank/}
3053 3065 assert_tag :textarea, :attributes => { :name => 'issue[notes]' }, :content => "\n"+notes
3054 3066 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
3055 3067 end
3056 3068
3057 3069 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
3058 3070 issue = Issue.find(2)
3059 3071 @request.session[:user_id] = 2
3060 3072
3061 3073 put :update,
3062 3074 :id => issue.id,
3063 3075 :issue => {
3064 3076 :fixed_version_id => 4
3065 3077 }
3066 3078
3067 3079 assert_response :redirect
3068 3080 issue.reload
3069 3081 assert_equal 4, issue.fixed_version_id
3070 3082 assert_not_equal issue.project_id, issue.fixed_version.project_id
3071 3083 end
3072 3084
3073 3085 def test_put_update_should_redirect_back_using_the_back_url_parameter
3074 3086 issue = Issue.find(2)
3075 3087 @request.session[:user_id] = 2
3076 3088
3077 3089 put :update,
3078 3090 :id => issue.id,
3079 3091 :issue => {
3080 3092 :fixed_version_id => 4
3081 3093 },
3082 3094 :back_url => '/issues'
3083 3095
3084 3096 assert_response :redirect
3085 3097 assert_redirected_to '/issues'
3086 3098 end
3087 3099
3088 3100 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3089 3101 issue = Issue.find(2)
3090 3102 @request.session[:user_id] = 2
3091 3103
3092 3104 put :update,
3093 3105 :id => issue.id,
3094 3106 :issue => {
3095 3107 :fixed_version_id => 4
3096 3108 },
3097 3109 :back_url => 'http://google.com'
3098 3110
3099 3111 assert_response :redirect
3100 3112 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
3101 3113 end
3102 3114
3103 3115 def test_get_bulk_edit
3104 3116 @request.session[:user_id] = 2
3105 3117 get :bulk_edit, :ids => [1, 2]
3106 3118 assert_response :success
3107 3119 assert_template 'bulk_edit'
3108 3120
3109 3121 assert_tag :select, :attributes => {:name => 'issue[project_id]'}
3110 3122 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
3111 3123
3112 3124 # Project specific custom field, date type
3113 3125 field = CustomField.find(9)
3114 3126 assert !field.is_for_all?
3115 3127 assert_equal 'date', field.field_format
3116 3128 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
3117 3129
3118 3130 # System wide custom field
3119 3131 assert CustomField.find(1).is_for_all?
3120 3132 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
3121 3133
3122 3134 # Be sure we don't display inactive IssuePriorities
3123 3135 assert ! IssuePriority.find(15).active?
3124 3136 assert_no_tag :option, :attributes => {:value => '15'},
3125 3137 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
3126 3138 end
3127 3139
3128 3140 def test_get_bulk_edit_on_different_projects
3129 3141 @request.session[:user_id] = 2
3130 3142 get :bulk_edit, :ids => [1, 2, 6]
3131 3143 assert_response :success
3132 3144 assert_template 'bulk_edit'
3133 3145
3134 3146 # Can not set issues from different projects as children of an issue
3135 3147 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
3136 3148
3137 3149 # Project specific custom field, date type
3138 3150 field = CustomField.find(9)
3139 3151 assert !field.is_for_all?
3140 3152 assert !field.project_ids.include?(Issue.find(6).project_id)
3141 3153 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
3142 3154 end
3143 3155
3144 3156 def test_get_bulk_edit_with_user_custom_field
3145 3157 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
3146 3158
3147 3159 @request.session[:user_id] = 2
3148 3160 get :bulk_edit, :ids => [1, 2]
3149 3161 assert_response :success
3150 3162 assert_template 'bulk_edit'
3151 3163
3152 3164 assert_tag :select,
3153 3165 :attributes => {:name => "issue[custom_field_values][#{field.id}]", :class => 'user_cf'},
3154 3166 :children => {
3155 3167 :only => {:tag => 'option'},
3156 3168 :count => Project.find(1).users.count + 2 # "no change" + "none" options
3157 3169 }
3158 3170 end
3159 3171
3160 3172 def test_get_bulk_edit_with_version_custom_field
3161 3173 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
3162 3174
3163 3175 @request.session[:user_id] = 2
3164 3176 get :bulk_edit, :ids => [1, 2]
3165 3177 assert_response :success
3166 3178 assert_template 'bulk_edit'
3167 3179
3168 3180 assert_tag :select,
3169 3181 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
3170 3182 :children => {
3171 3183 :only => {:tag => 'option'},
3172 3184 :count => Project.find(1).shared_versions.count + 2 # "no change" + "none" options
3173 3185 }
3174 3186 end
3175 3187
3176 3188 def test_get_bulk_edit_with_multi_custom_field
3177 3189 field = CustomField.find(1)
3178 3190 field.update_attribute :multiple, true
3179 3191
3180 3192 @request.session[:user_id] = 2
3181 3193 get :bulk_edit, :ids => [1, 2]
3182 3194 assert_response :success
3183 3195 assert_template 'bulk_edit'
3184 3196
3185 3197 assert_tag :select,
3186 3198 :attributes => {:name => "issue[custom_field_values][1][]"},
3187 3199 :children => {
3188 3200 :only => {:tag => 'option'},
3189 3201 :count => field.possible_values.size + 1 # "none" options
3190 3202 }
3191 3203 end
3192 3204
3193 3205 def test_bulk_edit_should_only_propose_statuses_allowed_for_all_issues
3194 3206 WorkflowTransition.delete_all
3195 3207 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 1)
3196 3208 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
3197 3209 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
3198 3210 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
3199 3211 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3)
3200 3212 WorkflowTransition.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
3201 3213 @request.session[:user_id] = 2
3202 3214 get :bulk_edit, :ids => [1, 2]
3203 3215
3204 3216 assert_response :success
3205 3217 statuses = assigns(:available_statuses)
3206 3218 assert_not_nil statuses
3207 3219 assert_equal [1, 3], statuses.map(&:id).sort
3208 3220
3209 3221 assert_tag 'select', :attributes => {:name => 'issue[status_id]'},
3210 3222 :children => {:count => 3} # 2 statuses + "no change" option
3211 3223 end
3212 3224
3213 3225 def test_bulk_edit_should_propose_target_project_open_shared_versions
3214 3226 @request.session[:user_id] = 2
3215 3227 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3216 3228 assert_response :success
3217 3229 assert_template 'bulk_edit'
3218 3230 assert_equal Project.find(1).shared_versions.open.all.sort, assigns(:versions).sort
3219 3231 assert_tag 'select',
3220 3232 :attributes => {:name => 'issue[fixed_version_id]'},
3221 3233 :descendant => {:tag => 'option', :content => '2.0'}
3222 3234 end
3223 3235
3224 3236 def test_bulk_edit_should_propose_target_project_categories
3225 3237 @request.session[:user_id] = 2
3226 3238 post :bulk_edit, :ids => [1, 2, 6], :issue => {:project_id => 1}
3227 3239 assert_response :success
3228 3240 assert_template 'bulk_edit'
3229 3241 assert_equal Project.find(1).issue_categories.sort, assigns(:categories).sort
3230 3242 assert_tag 'select',
3231 3243 :attributes => {:name => 'issue[category_id]'},
3232 3244 :descendant => {:tag => 'option', :content => 'Recipes'}
3233 3245 end
3234 3246
3235 3247 def test_bulk_update
3236 3248 @request.session[:user_id] = 2
3237 3249 # update issues priority
3238 3250 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3239 3251 :issue => {:priority_id => 7,
3240 3252 :assigned_to_id => '',
3241 3253 :custom_field_values => {'2' => ''}}
3242 3254
3243 3255 assert_response 302
3244 3256 # check that the issues were updated
3245 3257 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
3246 3258
3247 3259 issue = Issue.find(1)
3248 3260 journal = issue.journals.find(:first, :order => 'created_on DESC')
3249 3261 assert_equal '125', issue.custom_value_for(2).value
3250 3262 assert_equal 'Bulk editing', journal.notes
3251 3263 assert_equal 1, journal.details.size
3252 3264 end
3253 3265
3254 3266 def test_bulk_update_with_group_assignee
3255 3267 group = Group.find(11)
3256 3268 project = Project.find(1)
3257 3269 project.members << Member.new(:principal => group, :roles => [Role.givable.first])
3258 3270
3259 3271 @request.session[:user_id] = 2
3260 3272 # update issues assignee
3261 3273 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
3262 3274 :issue => {:priority_id => '',
3263 3275 :assigned_to_id => group.id,
3264 3276 :custom_field_values => {'2' => ''}}
3265 3277
3266 3278 assert_response 302
3267 3279 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
3268 3280 end
3269 3281
3270 3282 def test_bulk_update_on_different_projects
3271 3283 @request.session[:user_id] = 2
3272 3284 # update issues priority
3273 3285 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
3274 3286 :issue => {:priority_id => 7,
3275 3287 :assigned_to_id => '',
3276 3288 :custom_field_values => {'2' => ''}}
3277 3289
3278 3290 assert_response 302
3279 3291 # check that the issues were updated
3280 3292 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
3281 3293
3282 3294 issue = Issue.find(1)
3283 3295 journal = issue.journals.find(:first, :order => 'created_on DESC')
3284 3296 assert_equal '125', issue.custom_value_for(2).value
3285 3297 assert_equal 'Bulk editing', journal.notes
3286 3298 assert_equal 1, journal.details.size
3287 3299 end
3288 3300
3289 3301 def test_bulk_update_on_different_projects_without_rights
3290 3302 @request.session[:user_id] = 3
3291 3303 user = User.find(3)
3292 3304 action = { :controller => "issues", :action => "bulk_update" }
3293 3305 assert user.allowed_to?(action, Issue.find(1).project)
3294 3306 assert ! user.allowed_to?(action, Issue.find(6).project)
3295 3307 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
3296 3308 :issue => {:priority_id => 7,
3297 3309 :assigned_to_id => '',
3298 3310 :custom_field_values => {'2' => ''}}
3299 3311 assert_response 403
3300 3312 assert_not_equal "Bulk should fail", Journal.last.notes
3301 3313 end
3302 3314
3303 3315 def test_bullk_update_should_send_a_notification
3304 3316 @request.session[:user_id] = 2
3305 3317 ActionMailer::Base.deliveries.clear
3306 3318 post(:bulk_update,
3307 3319 {
3308 3320 :ids => [1, 2],
3309 3321 :notes => 'Bulk editing',
3310 3322 :issue => {
3311 3323 :priority_id => 7,
3312 3324 :assigned_to_id => '',
3313 3325 :custom_field_values => {'2' => ''}
3314 3326 }
3315 3327 })
3316 3328
3317 3329 assert_response 302
3318 3330 assert_equal 2, ActionMailer::Base.deliveries.size
3319 3331 end
3320 3332
3321 3333 def test_bulk_update_project
3322 3334 @request.session[:user_id] = 2
3323 3335 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}
3324 3336 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3325 3337 # Issues moved to project 2
3326 3338 assert_equal 2, Issue.find(1).project_id
3327 3339 assert_equal 2, Issue.find(2).project_id
3328 3340 # No tracker change
3329 3341 assert_equal 1, Issue.find(1).tracker_id
3330 3342 assert_equal 2, Issue.find(2).tracker_id
3331 3343 end
3332 3344
3333 3345 def test_bulk_update_project_on_single_issue_should_follow_when_needed
3334 3346 @request.session[:user_id] = 2
3335 3347 post :bulk_update, :id => 1, :issue => {:project_id => '2'}, :follow => '1'
3336 3348 assert_redirected_to '/issues/1'
3337 3349 end
3338 3350
3339 3351 def test_bulk_update_project_on_multiple_issues_should_follow_when_needed
3340 3352 @request.session[:user_id] = 2
3341 3353 post :bulk_update, :id => [1, 2], :issue => {:project_id => '2'}, :follow => '1'
3342 3354 assert_redirected_to '/projects/onlinestore/issues'
3343 3355 end
3344 3356
3345 3357 def test_bulk_update_tracker
3346 3358 @request.session[:user_id] = 2
3347 3359 post :bulk_update, :ids => [1, 2], :issue => {:tracker_id => '2'}
3348 3360 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3349 3361 assert_equal 2, Issue.find(1).tracker_id
3350 3362 assert_equal 2, Issue.find(2).tracker_id
3351 3363 end
3352 3364
3353 3365 def test_bulk_update_status
3354 3366 @request.session[:user_id] = 2
3355 3367 # update issues priority
3356 3368 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
3357 3369 :issue => {:priority_id => '',
3358 3370 :assigned_to_id => '',
3359 3371 :status_id => '5'}
3360 3372
3361 3373 assert_response 302
3362 3374 issue = Issue.find(1)
3363 3375 assert issue.closed?
3364 3376 end
3365 3377
3366 3378 def test_bulk_update_priority
3367 3379 @request.session[:user_id] = 2
3368 3380 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3369 3381
3370 3382 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3371 3383 assert_equal 6, Issue.find(1).priority_id
3372 3384 assert_equal 6, Issue.find(2).priority_id
3373 3385 end
3374 3386
3375 3387 def test_bulk_update_with_notes
3376 3388 @request.session[:user_id] = 2
3377 3389 post :bulk_update, :ids => [1, 2], :notes => 'Moving two issues'
3378 3390
3379 3391 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3380 3392 assert_equal 'Moving two issues', Issue.find(1).journals.sort_by(&:id).last.notes
3381 3393 assert_equal 'Moving two issues', Issue.find(2).journals.sort_by(&:id).last.notes
3382 3394 end
3383 3395
3384 3396 def test_bulk_update_parent_id
3385 3397 @request.session[:user_id] = 2
3386 3398 post :bulk_update, :ids => [1, 3],
3387 3399 :notes => 'Bulk editing parent',
3388 3400 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
3389 3401
3390 3402 assert_response 302
3391 3403 parent = Issue.find(2)
3392 3404 assert_equal parent.id, Issue.find(1).parent_id
3393 3405 assert_equal parent.id, Issue.find(3).parent_id
3394 3406 assert_equal [1, 3], parent.children.collect(&:id).sort
3395 3407 end
3396 3408
3397 3409 def test_bulk_update_custom_field
3398 3410 @request.session[:user_id] = 2
3399 3411 # update issues priority
3400 3412 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
3401 3413 :issue => {:priority_id => '',
3402 3414 :assigned_to_id => '',
3403 3415 :custom_field_values => {'2' => '777'}}
3404 3416
3405 3417 assert_response 302
3406 3418
3407 3419 issue = Issue.find(1)
3408 3420 journal = issue.journals.find(:first, :order => 'created_on DESC')
3409 3421 assert_equal '777', issue.custom_value_for(2).value
3410 3422 assert_equal 1, journal.details.size
3411 3423 assert_equal '125', journal.details.first.old_value
3412 3424 assert_equal '777', journal.details.first.value
3413 3425 end
3414 3426
3415 3427 def test_bulk_update_custom_field_to_blank
3416 3428 @request.session[:user_id] = 2
3417 3429 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
3418 3430 :issue => {:priority_id => '',
3419 3431 :assigned_to_id => '',
3420 3432 :custom_field_values => {'1' => '__none__'}}
3421 3433 assert_response 302
3422 3434 assert_equal '', Issue.find(1).custom_field_value(1)
3423 3435 assert_equal '', Issue.find(3).custom_field_value(1)
3424 3436 end
3425 3437
3426 3438 def test_bulk_update_multi_custom_field
3427 3439 field = CustomField.find(1)
3428 3440 field.update_attribute :multiple, true
3429 3441
3430 3442 @request.session[:user_id] = 2
3431 3443 post :bulk_update, :ids => [1, 2, 3], :notes => 'Bulk editing multi custom field',
3432 3444 :issue => {:priority_id => '',
3433 3445 :assigned_to_id => '',
3434 3446 :custom_field_values => {'1' => ['MySQL', 'Oracle']}}
3435 3447
3436 3448 assert_response 302
3437 3449
3438 3450 assert_equal ['MySQL', 'Oracle'], Issue.find(1).custom_field_value(1).sort
3439 3451 assert_equal ['MySQL', 'Oracle'], Issue.find(3).custom_field_value(1).sort
3440 3452 # the custom field is not associated with the issue tracker
3441 3453 assert_nil Issue.find(2).custom_field_value(1)
3442 3454 end
3443 3455
3444 3456 def test_bulk_update_multi_custom_field_to_blank
3445 3457 field = CustomField.find(1)
3446 3458 field.update_attribute :multiple, true
3447 3459
3448 3460 @request.session[:user_id] = 2
3449 3461 post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
3450 3462 :issue => {:priority_id => '',
3451 3463 :assigned_to_id => '',
3452 3464 :custom_field_values => {'1' => ['__none__']}}
3453 3465 assert_response 302
3454 3466 assert_equal [''], Issue.find(1).custom_field_value(1)
3455 3467 assert_equal [''], Issue.find(3).custom_field_value(1)
3456 3468 end
3457 3469
3458 3470 def test_bulk_update_unassign
3459 3471 assert_not_nil Issue.find(2).assigned_to
3460 3472 @request.session[:user_id] = 2
3461 3473 # unassign issues
3462 3474 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
3463 3475 assert_response 302
3464 3476 # check that the issues were updated
3465 3477 assert_nil Issue.find(2).assigned_to
3466 3478 end
3467 3479
3468 3480 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
3469 3481 @request.session[:user_id] = 2
3470 3482
3471 3483 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
3472 3484
3473 3485 assert_response :redirect
3474 3486 issues = Issue.find([1,2])
3475 3487 issues.each do |issue|
3476 3488 assert_equal 4, issue.fixed_version_id
3477 3489 assert_not_equal issue.project_id, issue.fixed_version.project_id
3478 3490 end
3479 3491 end
3480 3492
3481 3493 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
3482 3494 @request.session[:user_id] = 2
3483 3495 post :bulk_update, :ids => [1,2], :back_url => '/issues'
3484 3496
3485 3497 assert_response :redirect
3486 3498 assert_redirected_to '/issues'
3487 3499 end
3488 3500
3489 3501 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
3490 3502 @request.session[:user_id] = 2
3491 3503 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
3492 3504
3493 3505 assert_response :redirect
3494 3506 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
3495 3507 end
3496 3508
3497 3509 def test_bulk_update_with_failure_should_set_flash
3498 3510 @request.session[:user_id] = 2
3499 3511 Issue.update_all("subject = ''", "id = 2") # Make it invalid
3500 3512 post :bulk_update, :ids => [1, 2], :issue => {:priority_id => 6}
3501 3513
3502 3514 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => 'ecookbook'
3503 3515 assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
3504 3516 end
3505 3517
3506 3518 def test_get_bulk_copy
3507 3519 @request.session[:user_id] = 2
3508 3520 get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
3509 3521 assert_response :success
3510 3522 assert_template 'bulk_edit'
3511 3523
3512 3524 issues = assigns(:issues)
3513 3525 assert_not_nil issues
3514 3526 assert_equal [1, 2, 3], issues.map(&:id).sort
3515 3527
3516 3528 assert_select 'input[name=copy_attachments]'
3517 3529 end
3518 3530
3519 3531 def test_bulk_copy_to_another_project
3520 3532 @request.session[:user_id] = 2
3521 3533 assert_difference 'Issue.count', 2 do
3522 3534 assert_no_difference 'Project.find(1).issues.count' do
3523 3535 post :bulk_update, :ids => [1, 2], :issue => {:project_id => '2'}, :copy => '1'
3524 3536 end
3525 3537 end
3526 3538 assert_redirected_to '/projects/ecookbook/issues'
3527 3539
3528 3540 copies = Issue.all(:order => 'id DESC', :limit => issues.size)
3529 3541 copies.each do |copy|
3530 3542 assert_equal 2, copy.project_id
3531 3543 end
3532 3544 end
3533 3545
3534 3546 def test_bulk_copy_should_allow_not_changing_the_issue_attributes
3535 3547 @request.session[:user_id] = 2
3536 3548 issues = [
3537 3549 Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, :priority_id => 2, :subject => 'issue 1', :author_id => 1, :assigned_to_id => nil),
3538 3550 Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, :priority_id => 1, :subject => 'issue 2', :author_id => 2, :assigned_to_id => 3)
3539 3551 ]
3540 3552
3541 3553 assert_difference 'Issue.count', issues.size do
3542 3554 post :bulk_update, :ids => issues.map(&:id), :copy => '1',
3543 3555 :issue => {
3544 3556 :project_id => '', :tracker_id => '', :assigned_to_id => '',
3545 3557 :status_id => '', :start_date => '', :due_date => ''
3546 3558 }
3547 3559 end
3548 3560
3549 3561 copies = Issue.all(:order => 'id DESC', :limit => issues.size)
3550 3562 issues.each do |orig|
3551 3563 copy = copies.detect {|c| c.subject == orig.subject}
3552 3564 assert_not_nil copy
3553 3565 assert_equal orig.project_id, copy.project_id
3554 3566 assert_equal orig.tracker_id, copy.tracker_id
3555 3567 assert_equal orig.status_id, copy.status_id
3556 3568 assert_equal orig.assigned_to_id, copy.assigned_to_id
3557 3569 assert_equal orig.priority_id, copy.priority_id
3558 3570 end
3559 3571 end
3560 3572
3561 3573 def test_bulk_copy_should_allow_changing_the_issue_attributes
3562 3574 # Fixes random test failure with Mysql
3563 3575 # where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3564 3576 # doesn't return the expected results
3565 3577 Issue.delete_all("project_id=2")
3566 3578
3567 3579 @request.session[:user_id] = 2
3568 3580 assert_difference 'Issue.count', 2 do
3569 3581 assert_no_difference 'Project.find(1).issues.count' do
3570 3582 post :bulk_update, :ids => [1, 2], :copy => '1',
3571 3583 :issue => {
3572 3584 :project_id => '2', :tracker_id => '', :assigned_to_id => '4',
3573 3585 :status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
3574 3586 }
3575 3587 end
3576 3588 end
3577 3589
3578 3590 copied_issues = Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
3579 3591 assert_equal 2, copied_issues.size
3580 3592 copied_issues.each do |issue|
3581 3593 assert_equal 2, issue.project_id, "Project is incorrect"
3582 3594 assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
3583 3595 assert_equal 1, issue.status_id, "Status is incorrect"
3584 3596 assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
3585 3597 assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
3586 3598 end
3587 3599 end
3588 3600
3589 3601 def test_bulk_copy_should_allow_adding_a_note
3590 3602 @request.session[:user_id] = 2
3591 3603 assert_difference 'Issue.count', 1 do
3592 3604 post :bulk_update, :ids => [1], :copy => '1',
3593 3605 :notes => 'Copying one issue',
3594 3606 :issue => {
3595 3607 :project_id => '', :tracker_id => '', :assigned_to_id => '4',
3596 3608 :status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
3597 3609 }
3598 3610 end
3599 3611
3600 3612 issue = Issue.first(:order => 'id DESC')
3601 3613 assert_equal 1, issue.journals.size
3602 3614 journal = issue.journals.first
3603 3615 assert_equal 0, journal.details.size
3604 3616 assert_equal 'Copying one issue', journal.notes
3605 3617 end
3606 3618
3607 3619 def test_bulk_copy_should_allow_not_copying_the_attachments
3608 3620 attachment_count = Issue.find(3).attachments.size
3609 3621 assert attachment_count > 0
3610 3622 @request.session[:user_id] = 2
3611 3623
3612 3624 assert_difference 'Issue.count', 1 do
3613 3625 assert_no_difference 'Attachment.count' do
3614 3626 post :bulk_update, :ids => [3], :copy => '1',
3615 3627 :issue => {
3616 3628 :project_id => ''
3617 3629 }
3618 3630 end
3619 3631 end
3620 3632 end
3621 3633
3622 3634 def test_bulk_copy_should_allow_copying_the_attachments
3623 3635 attachment_count = Issue.find(3).attachments.size
3624 3636 assert attachment_count > 0
3625 3637 @request.session[:user_id] = 2
3626 3638
3627 3639 assert_difference 'Issue.count', 1 do
3628 3640 assert_difference 'Attachment.count', attachment_count do
3629 3641 post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
3630 3642 :issue => {
3631 3643 :project_id => ''
3632 3644 }
3633 3645 end
3634 3646 end
3635 3647 end
3636 3648
3637 3649 def test_bulk_copy_should_add_relations_with_copied_issues
3638 3650 @request.session[:user_id] = 2
3639 3651
3640 3652 assert_difference 'Issue.count', 2 do
3641 3653 assert_difference 'IssueRelation.count', 2 do
3642 3654 post :bulk_update, :ids => [1, 3], :copy => '1',
3643 3655 :issue => {
3644 3656 :project_id => '1'
3645 3657 }
3646 3658 end
3647 3659 end
3648 3660 end
3649 3661
3650 3662 def test_bulk_copy_should_allow_not_copying_the_subtasks
3651 3663 issue = Issue.generate_with_descendants!
3652 3664 @request.session[:user_id] = 2
3653 3665
3654 3666 assert_difference 'Issue.count', 1 do
3655 3667 post :bulk_update, :ids => [issue.id], :copy => '1',
3656 3668 :issue => {
3657 3669 :project_id => ''
3658 3670 }
3659 3671 end
3660 3672 end
3661 3673
3662 3674 def test_bulk_copy_should_allow_copying_the_subtasks
3663 3675 issue = Issue.generate_with_descendants!
3664 3676 count = issue.descendants.count
3665 3677 @request.session[:user_id] = 2
3666 3678
3667 3679 assert_difference 'Issue.count', count+1 do
3668 3680 post :bulk_update, :ids => [issue.id], :copy => '1', :copy_subtasks => '1',
3669 3681 :issue => {
3670 3682 :project_id => ''
3671 3683 }
3672 3684 end
3673 3685 copy = Issue.where(:parent_id => nil).order("id DESC").first
3674 3686 assert_equal count, copy.descendants.count
3675 3687 end
3676 3688
3677 3689 def test_bulk_copy_should_not_copy_selected_subtasks_twice
3678 3690 issue = Issue.generate_with_descendants!
3679 3691 count = issue.descendants.count
3680 3692 @request.session[:user_id] = 2
3681 3693
3682 3694 assert_difference 'Issue.count', count+1 do
3683 3695 post :bulk_update, :ids => issue.self_and_descendants.map(&:id), :copy => '1', :copy_subtasks => '1',
3684 3696 :issue => {
3685 3697 :project_id => ''
3686 3698 }
3687 3699 end
3688 3700 copy = Issue.where(:parent_id => nil).order("id DESC").first
3689 3701 assert_equal count, copy.descendants.count
3690 3702 end
3691 3703
3692 3704 def test_bulk_copy_to_another_project_should_follow_when_needed
3693 3705 @request.session[:user_id] = 2
3694 3706 post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'
3695 3707 issue = Issue.first(:order => 'id DESC')
3696 3708 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
3697 3709 end
3698 3710
3699 3711 def test_destroy_issue_with_no_time_entries
3700 3712 assert_nil TimeEntry.find_by_issue_id(2)
3701 3713 @request.session[:user_id] = 2
3702 3714
3703 3715 assert_difference 'Issue.count', -1 do
3704 3716 delete :destroy, :id => 2
3705 3717 end
3706 3718 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3707 3719 assert_nil Issue.find_by_id(2)
3708 3720 end
3709 3721
3710 3722 def test_destroy_issues_with_time_entries
3711 3723 @request.session[:user_id] = 2
3712 3724
3713 3725 assert_no_difference 'Issue.count' do
3714 3726 delete :destroy, :ids => [1, 3]
3715 3727 end
3716 3728 assert_response :success
3717 3729 assert_template 'destroy'
3718 3730 assert_not_nil assigns(:hours)
3719 3731 assert Issue.find_by_id(1) && Issue.find_by_id(3)
3720 3732 assert_tag 'form',
3721 3733 :descendant => {:tag => 'input', :attributes => {:name => '_method', :value => 'delete'}}
3722 3734 end
3723 3735
3724 3736 def test_destroy_issues_and_destroy_time_entries
3725 3737 @request.session[:user_id] = 2
3726 3738
3727 3739 assert_difference 'Issue.count', -2 do
3728 3740 assert_difference 'TimeEntry.count', -3 do
3729 3741 delete :destroy, :ids => [1, 3], :todo => 'destroy'
3730 3742 end
3731 3743 end
3732 3744 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3733 3745 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3734 3746 assert_nil TimeEntry.find_by_id([1, 2])
3735 3747 end
3736 3748
3737 3749 def test_destroy_issues_and_assign_time_entries_to_project
3738 3750 @request.session[:user_id] = 2
3739 3751
3740 3752 assert_difference 'Issue.count', -2 do
3741 3753 assert_no_difference 'TimeEntry.count' do
3742 3754 delete :destroy, :ids => [1, 3], :todo => 'nullify'
3743 3755 end
3744 3756 end
3745 3757 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3746 3758 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3747 3759 assert_nil TimeEntry.find(1).issue_id
3748 3760 assert_nil TimeEntry.find(2).issue_id
3749 3761 end
3750 3762
3751 3763 def test_destroy_issues_and_reassign_time_entries_to_another_issue
3752 3764 @request.session[:user_id] = 2
3753 3765
3754 3766 assert_difference 'Issue.count', -2 do
3755 3767 assert_no_difference 'TimeEntry.count' do
3756 3768 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
3757 3769 end
3758 3770 end
3759 3771 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
3760 3772 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
3761 3773 assert_equal 2, TimeEntry.find(1).issue_id
3762 3774 assert_equal 2, TimeEntry.find(2).issue_id
3763 3775 end
3764 3776
3765 3777 def test_destroy_issues_from_different_projects
3766 3778 @request.session[:user_id] = 2
3767 3779
3768 3780 assert_difference 'Issue.count', -3 do
3769 3781 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
3770 3782 end
3771 3783 assert_redirected_to :controller => 'issues', :action => 'index'
3772 3784 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
3773 3785 end
3774 3786
3775 3787 def test_destroy_parent_and_child_issues
3776 3788 parent = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Parent Issue')
3777 3789 child = Issue.create!(:project_id => 1, :author_id => 1, :tracker_id => 1, :subject => 'Child Issue', :parent_issue_id => parent.id)
3778 3790 assert child.is_descendant_of?(parent.reload)
3779 3791
3780 3792 @request.session[:user_id] = 2
3781 3793 assert_difference 'Issue.count', -2 do
3782 3794 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
3783 3795 end
3784 3796 assert_response 302
3785 3797 end
3786 3798
3787 3799 def test_default_search_scope
3788 3800 get :index
3789 3801 assert_tag :div, :attributes => {:id => 'quick-search'},
3790 3802 :child => {:tag => 'form',
3791 3803 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
3792 3804 end
3793 3805 end
General Comments 0
You need to be logged in to leave comments. Login now