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