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