##// END OF EJS Templates
Display issue form fields according to permissions....
Jean-Philippe Lang -
r8107:b6e6f557f01b
parent child
Show More
@@ -1,986 +1,987
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 class Issue < ActiveRecord::Base
19 19 include Redmine::SafeAttributes
20 20
21 21 belongs_to :project
22 22 belongs_to :tracker
23 23 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
24 24 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
25 25 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
26 26 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
27 27 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
28 28 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
29 29
30 30 has_many :journals, :as => :journalized, :dependent => :destroy
31 31 has_many :time_entries, :dependent => :delete_all
32 32 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
33 33
34 34 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
35 35 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
36 36
37 37 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
38 38 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
39 39 acts_as_customizable
40 40 acts_as_watchable
41 41 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
42 42 :include => [:project, :journals],
43 43 # sort by id so that limited eager loading doesn't break with postgresql
44 44 :order_column => "#{table_name}.id"
45 45 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
46 46 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
47 47 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
48 48
49 49 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
50 50 :author_key => :author_id
51 51
52 52 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
53 53
54 54 attr_reader :current_journal
55 55
56 56 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
57 57
58 58 validates_length_of :subject, :maximum => 255
59 59 validates_inclusion_of :done_ratio, :in => 0..100
60 60 validates_numericality_of :estimated_hours, :allow_nil => true
61 61 validate :validate_issue
62 62
63 63 named_scope :visible, lambda {|*args| { :include => :project,
64 64 :conditions => Issue.visible_condition(args.shift || User.current, *args) } }
65 65
66 66 named_scope :open, :conditions => ["#{IssueStatus.table_name}.is_closed = ?", false], :include => :status
67 67
68 68 named_scope :recently_updated, :order => "#{Issue.table_name}.updated_on DESC"
69 69 named_scope :with_limit, lambda { |limit| { :limit => limit} }
70 70 named_scope :on_active_project, :include => [:status, :project, :tracker],
71 71 :conditions => ["#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"]
72 72
73 73 named_scope :without_version, lambda {
74 74 {
75 75 :conditions => { :fixed_version_id => nil}
76 76 }
77 77 }
78 78
79 79 named_scope :with_query, lambda {|query|
80 80 {
81 81 :conditions => Query.merge_conditions(query.statement)
82 82 }
83 83 }
84 84
85 85 before_create :default_assign
86 86 before_save :close_duplicates, :update_done_ratio_from_issue_status
87 87 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
88 88 after_destroy :update_parent_attributes
89 89
90 90 # Returns a SQL conditions string used to find all issues visible by the specified user
91 91 def self.visible_condition(user, options={})
92 92 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
93 93 case role.issues_visibility
94 94 when 'all'
95 95 nil
96 96 when 'default'
97 97 user_ids = [user.id] + user.groups.map(&:id)
98 98 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
99 99 when 'own'
100 100 user_ids = [user.id] + user.groups.map(&:id)
101 101 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
102 102 else
103 103 '1=0'
104 104 end
105 105 end
106 106 end
107 107
108 108 # Returns true if usr or current user is allowed to view the issue
109 109 def visible?(usr=nil)
110 110 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
111 111 case role.issues_visibility
112 112 when 'all'
113 113 true
114 114 when 'default'
115 115 !self.is_private? || self.author == user || user.is_or_belongs_to?(assigned_to)
116 116 when 'own'
117 117 self.author == user || user.is_or_belongs_to?(assigned_to)
118 118 else
119 119 false
120 120 end
121 121 end
122 122 end
123 123
124 124 def after_initialize
125 125 if new_record?
126 126 # set default values for new records only
127 127 self.status ||= IssueStatus.default
128 128 self.priority ||= IssuePriority.default
129 129 end
130 130 end
131 131
132 132 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
133 133 def available_custom_fields
134 134 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
135 135 end
136 136
137 137 def copy_from(arg)
138 138 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
139 139 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
140 140 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
141 141 self.status = issue.status
142 142 self
143 143 end
144 144
145 145 # Moves/copies an issue to a new project and tracker
146 146 # Returns the moved/copied issue on success, false on failure
147 147 def move_to_project(*args)
148 148 ret = Issue.transaction do
149 149 move_to_project_without_transaction(*args) || raise(ActiveRecord::Rollback)
150 150 end || false
151 151 end
152 152
153 153 def move_to_project_without_transaction(new_project, new_tracker = nil, options = {})
154 154 options ||= {}
155 155
156 156 if options[:copy]
157 157 issue = self.class.new.copy_from(self)
158 158 else
159 159 issue = self
160 160 issue.init_journal(User.current, options[:notes])
161 161 end
162 162
163 163 if new_project && issue.project_id != new_project.id
164 164 # delete issue relations
165 165 unless Setting.cross_project_issue_relations?
166 166 issue.relations_from.clear
167 167 issue.relations_to.clear
168 168 end
169 169 # issue is moved to another project
170 170 # reassign to the category with same name if any
171 171 new_category = issue.category.nil? ? nil : new_project.issue_categories.find_by_name(issue.category.name)
172 172 issue.category = new_category
173 173 # Keep the fixed_version if it's still valid in the new_project
174 174 unless new_project.shared_versions.include?(issue.fixed_version)
175 175 issue.fixed_version = nil
176 176 end
177 177 issue.project = new_project
178 178 if issue.parent && issue.parent.project_id != issue.project_id
179 179 issue.parent_issue_id = nil
180 180 end
181 181 end
182 182 if new_tracker
183 183 issue.tracker = new_tracker
184 184 issue.reset_custom_values!
185 185 end
186 186 if options[:copy]
187 187 issue.author = User.current
188 188 issue.custom_field_values = self.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
189 189 issue.status = if options[:attributes] && options[:attributes][:status_id]
190 190 IssueStatus.find_by_id(options[:attributes][:status_id])
191 191 else
192 192 self.status
193 193 end
194 194 end
195 195 # Allow bulk setting of attributes on the issue
196 196 if options[:attributes]
197 197 issue.attributes = options[:attributes]
198 198 end
199 199 if options[:copy] && options[:notes].present?
200 200 issue.init_journal(User.current, options[:notes])
201 201 issue.current_journal.notify = false
202 202 end
203 203 if issue.save
204 204 unless options[:copy]
205 205 # Manually update project_id on related time entries
206 206 TimeEntry.update_all("project_id = #{new_project.id}", {:issue_id => id})
207 207
208 208 issue.children.each do |child|
209 209 unless child.move_to_project_without_transaction(new_project)
210 210 # Move failed and transaction was rollback'd
211 211 return false
212 212 end
213 213 end
214 214 end
215 215 else
216 216 return false
217 217 end
218 218 issue
219 219 end
220 220
221 221 def status_id=(sid)
222 222 self.status = nil
223 223 write_attribute(:status_id, sid)
224 224 end
225 225
226 226 def priority_id=(pid)
227 227 self.priority = nil
228 228 write_attribute(:priority_id, pid)
229 229 end
230 230
231 231 def tracker_id=(tid)
232 232 self.tracker = nil
233 233 result = write_attribute(:tracker_id, tid)
234 234 @custom_field_values = nil
235 235 result
236 236 end
237 237
238 238 def description=(arg)
239 239 if arg.is_a?(String)
240 240 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
241 241 end
242 242 write_attribute(:description, arg)
243 243 end
244 244
245 245 # Overrides attributes= so that project and tracker get assigned first
246 246 def attributes_with_project_and_tracker_first=(new_attributes, *args)
247 247 return if new_attributes.nil?
248 248 attrs = new_attributes.dup
249 249 attrs.stringify_keys!
250 250
251 251 %w(project project_id tracker tracker_id).each do |attr|
252 252 if attrs.has_key?(attr)
253 253 send "#{attr}=", attrs.delete(attr)
254 254 end
255 255 end
256 256 send :attributes_without_project_and_tracker_first=, attrs, *args
257 257 end
258 258 # Do not redefine alias chain on reload (see #4838)
259 259 alias_method_chain(:attributes=, :project_and_tracker_first) unless method_defined?(:attributes_without_project_and_tracker_first=)
260 260
261 261 def estimated_hours=(h)
262 262 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
263 263 end
264 264
265 265 safe_attributes 'tracker_id',
266 266 'status_id',
267 267 'category_id',
268 268 'assigned_to_id',
269 269 'priority_id',
270 270 'fixed_version_id',
271 271 'subject',
272 272 'description',
273 273 'start_date',
274 274 'due_date',
275 275 'done_ratio',
276 276 'estimated_hours',
277 277 'custom_field_values',
278 278 'custom_fields',
279 279 'lock_version',
280 280 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
281 281
282 282 safe_attributes 'status_id',
283 283 'assigned_to_id',
284 284 'fixed_version_id',
285 285 'done_ratio',
286 'lock_version',
286 287 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
287 288
288 289 safe_attributes 'watcher_user_ids',
289 290 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
290 291
291 292 safe_attributes 'is_private',
292 293 :if => lambda {|issue, user|
293 294 user.allowed_to?(:set_issues_private, issue.project) ||
294 295 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
295 296 }
296 297
297 298 safe_attributes 'parent_issue_id',
298 299 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
299 300 user.allowed_to?(:manage_subtasks, issue.project)}
300 301
301 302 # Safely sets attributes
302 303 # Should be called from controllers instead of #attributes=
303 304 # attr_accessible is too rough because we still want things like
304 305 # Issue.new(:project => foo) to work
305 306 # TODO: move workflow/permission checks from controllers to here
306 307 def safe_attributes=(attrs, user=User.current)
307 308 return unless attrs.is_a?(Hash)
308 309
309 310 # User can change issue attributes only if he has :edit permission or if a workflow transition is allowed
310 311 attrs = delete_unsafe_attributes(attrs, user)
311 312 return if attrs.empty?
312 313
313 314 # Tracker must be set before since new_statuses_allowed_to depends on it.
314 315 if t = attrs.delete('tracker_id')
315 316 self.tracker_id = t
316 317 end
317 318
318 319 if attrs['status_id']
319 320 unless new_statuses_allowed_to(user).collect(&:id).include?(attrs['status_id'].to_i)
320 321 attrs.delete('status_id')
321 322 end
322 323 end
323 324
324 325 unless leaf?
325 326 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
326 327 end
327 328
328 329 if attrs['parent_issue_id'].present?
329 330 attrs.delete('parent_issue_id') unless Issue.visible(user).exists?(attrs['parent_issue_id'].to_i)
330 331 end
331 332
332 333 # mass-assignment security bypass
333 334 self.send :attributes=, attrs, false
334 335 end
335 336
336 337 def done_ratio
337 338 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
338 339 status.default_done_ratio
339 340 else
340 341 read_attribute(:done_ratio)
341 342 end
342 343 end
343 344
344 345 def self.use_status_for_done_ratio?
345 346 Setting.issue_done_ratio == 'issue_status'
346 347 end
347 348
348 349 def self.use_field_for_done_ratio?
349 350 Setting.issue_done_ratio == 'issue_field'
350 351 end
351 352
352 353 def validate_issue
353 354 if self.due_date.nil? && @attributes['due_date'] && !@attributes['due_date'].empty?
354 355 errors.add :due_date, :not_a_date
355 356 end
356 357
357 358 if self.due_date and self.start_date and self.due_date < self.start_date
358 359 errors.add :due_date, :greater_than_start_date
359 360 end
360 361
361 362 if start_date && soonest_start && start_date < soonest_start
362 363 errors.add :start_date, :invalid
363 364 end
364 365
365 366 if fixed_version
366 367 if !assignable_versions.include?(fixed_version)
367 368 errors.add :fixed_version_id, :inclusion
368 369 elsif reopened? && fixed_version.closed?
369 370 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
370 371 end
371 372 end
372 373
373 374 # Checks that the issue can not be added/moved to a disabled tracker
374 375 if project && (tracker_id_changed? || project_id_changed?)
375 376 unless project.trackers.include?(tracker)
376 377 errors.add :tracker_id, :inclusion
377 378 end
378 379 end
379 380
380 381 # Checks parent issue assignment
381 382 if @parent_issue
382 383 if @parent_issue.project_id != project_id
383 384 errors.add :parent_issue_id, :not_same_project
384 385 elsif !new_record?
385 386 # moving an existing issue
386 387 if @parent_issue.root_id != root_id
387 388 # we can always move to another tree
388 389 elsif move_possible?(@parent_issue)
389 390 # move accepted inside tree
390 391 else
391 392 errors.add :parent_issue_id, :not_a_valid_parent
392 393 end
393 394 end
394 395 end
395 396 end
396 397
397 398 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
398 399 # even if the user turns off the setting later
399 400 def update_done_ratio_from_issue_status
400 401 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
401 402 self.done_ratio = status.default_done_ratio
402 403 end
403 404 end
404 405
405 406 def init_journal(user, notes = "")
406 407 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
407 408 @issue_before_change = self.clone
408 409 @issue_before_change.status = self.status
409 410 @custom_values_before_change = {}
410 411 self.custom_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
411 412 # Make sure updated_on is updated when adding a note.
412 413 updated_on_will_change!
413 414 @current_journal
414 415 end
415 416
416 417 # Return true if the issue is closed, otherwise false
417 418 def closed?
418 419 self.status.is_closed?
419 420 end
420 421
421 422 # Return true if the issue is being reopened
422 423 def reopened?
423 424 if !new_record? && status_id_changed?
424 425 status_was = IssueStatus.find_by_id(status_id_was)
425 426 status_new = IssueStatus.find_by_id(status_id)
426 427 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
427 428 return true
428 429 end
429 430 end
430 431 false
431 432 end
432 433
433 434 # Return true if the issue is being closed
434 435 def closing?
435 436 if !new_record? && status_id_changed?
436 437 status_was = IssueStatus.find_by_id(status_id_was)
437 438 status_new = IssueStatus.find_by_id(status_id)
438 439 if status_was && status_new && !status_was.is_closed? && status_new.is_closed?
439 440 return true
440 441 end
441 442 end
442 443 false
443 444 end
444 445
445 446 # Returns true if the issue is overdue
446 447 def overdue?
447 448 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
448 449 end
449 450
450 451 # Is the amount of work done less than it should for the due date
451 452 def behind_schedule?
452 453 return false if start_date.nil? || due_date.nil?
453 454 done_date = start_date + ((due_date - start_date+1)* done_ratio/100).floor
454 455 return done_date <= Date.today
455 456 end
456 457
457 458 # Does this issue have children?
458 459 def children?
459 460 !leaf?
460 461 end
461 462
462 463 # Users the issue can be assigned to
463 464 def assignable_users
464 465 users = project.assignable_users
465 466 users << author if author
466 467 users << assigned_to if assigned_to
467 468 users.uniq.sort
468 469 end
469 470
470 471 # Versions that the issue can be assigned to
471 472 def assignable_versions
472 473 @assignable_versions ||= (project.shared_versions.open + [Version.find_by_id(fixed_version_id_was)]).compact.uniq.sort
473 474 end
474 475
475 476 # Returns true if this issue is blocked by another issue that is still open
476 477 def blocked?
477 478 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
478 479 end
479 480
480 481 # Returns an array of status that user is able to apply
481 482 def new_statuses_allowed_to(user, include_default=false)
482 483 statuses = status.find_new_statuses_allowed_to(
483 484 user.roles_for_project(project),
484 485 tracker,
485 486 author == user,
486 487 assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
487 488 )
488 489 statuses << status unless statuses.empty?
489 490 statuses << IssueStatus.default if include_default
490 491 statuses = statuses.uniq.sort
491 492 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
492 493 end
493 494
494 495 # Returns the mail adresses of users that should be notified
495 496 def recipients
496 497 notified = project.notified_users
497 498 # Author and assignee are always notified unless they have been
498 499 # locked or don't want to be notified
499 500 notified << author if author && author.active? && author.notify_about?(self)
500 501 if assigned_to
501 502 if assigned_to.is_a?(Group)
502 503 notified += assigned_to.users.select {|u| u.active? && u.notify_about?(self)}
503 504 else
504 505 notified << assigned_to if assigned_to.active? && assigned_to.notify_about?(self)
505 506 end
506 507 end
507 508 notified.uniq!
508 509 # Remove users that can not view the issue
509 510 notified.reject! {|user| !visible?(user)}
510 511 notified.collect(&:mail)
511 512 end
512 513
513 514 # Returns the number of hours spent on this issue
514 515 def spent_hours
515 516 @spent_hours ||= time_entries.sum(:hours) || 0
516 517 end
517 518
518 519 # Returns the total number of hours spent on this issue and its descendants
519 520 #
520 521 # Example:
521 522 # spent_hours => 0.0
522 523 # spent_hours => 50.2
523 524 def total_spent_hours
524 525 @total_spent_hours ||= self_and_descendants.sum("#{TimeEntry.table_name}.hours", :include => :time_entries).to_f || 0.0
525 526 end
526 527
527 528 def relations
528 529 @relations ||= (relations_from + relations_to).sort
529 530 end
530 531
531 532 # Preloads relations for a collection of issues
532 533 def self.load_relations(issues)
533 534 if issues.any?
534 535 relations = IssueRelation.all(:conditions => ["issue_from_id IN (:ids) OR issue_to_id IN (:ids)", {:ids => issues.map(&:id)}])
535 536 issues.each do |issue|
536 537 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
537 538 end
538 539 end
539 540 end
540 541
541 542 # Preloads visible spent time for a collection of issues
542 543 def self.load_visible_spent_hours(issues, user=User.current)
543 544 if issues.any?
544 545 hours_by_issue_id = TimeEntry.visible(user).sum(:hours, :group => :issue_id)
545 546 issues.each do |issue|
546 547 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
547 548 end
548 549 end
549 550 end
550 551
551 552 # Finds an issue relation given its id.
552 553 def find_relation(relation_id)
553 554 IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
554 555 end
555 556
556 557 def all_dependent_issues(except=[])
557 558 except << self
558 559 dependencies = []
559 560 relations_from.each do |relation|
560 561 if relation.issue_to && !except.include?(relation.issue_to)
561 562 dependencies << relation.issue_to
562 563 dependencies += relation.issue_to.all_dependent_issues(except)
563 564 end
564 565 end
565 566 dependencies
566 567 end
567 568
568 569 # Returns an array of issues that duplicate this one
569 570 def duplicates
570 571 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
571 572 end
572 573
573 574 # Returns the due date or the target due date if any
574 575 # Used on gantt chart
575 576 def due_before
576 577 due_date || (fixed_version ? fixed_version.effective_date : nil)
577 578 end
578 579
579 580 # Returns the time scheduled for this issue.
580 581 #
581 582 # Example:
582 583 # Start Date: 2/26/09, End Date: 3/04/09
583 584 # duration => 6
584 585 def duration
585 586 (start_date && due_date) ? due_date - start_date : 0
586 587 end
587 588
588 589 def soonest_start
589 590 @soonest_start ||= (
590 591 relations_to.collect{|relation| relation.successor_soonest_start} +
591 592 ancestors.collect(&:soonest_start)
592 593 ).compact.max
593 594 end
594 595
595 596 def reschedule_after(date)
596 597 return if date.nil?
597 598 if leaf?
598 599 if start_date.nil? || start_date < date
599 600 self.start_date, self.due_date = date, date + duration
600 601 save
601 602 end
602 603 else
603 604 leaves.each do |leaf|
604 605 leaf.reschedule_after(date)
605 606 end
606 607 end
607 608 end
608 609
609 610 def <=>(issue)
610 611 if issue.nil?
611 612 -1
612 613 elsif root_id != issue.root_id
613 614 (root_id || 0) <=> (issue.root_id || 0)
614 615 else
615 616 (lft || 0) <=> (issue.lft || 0)
616 617 end
617 618 end
618 619
619 620 def to_s
620 621 "#{tracker} ##{id}: #{subject}"
621 622 end
622 623
623 624 # Returns a string of css classes that apply to the issue
624 625 def css_classes
625 626 s = "issue status-#{status.position} priority-#{priority.position}"
626 627 s << ' closed' if closed?
627 628 s << ' overdue' if overdue?
628 629 s << ' child' if child?
629 630 s << ' parent' unless leaf?
630 631 s << ' private' if is_private?
631 632 s << ' created-by-me' if User.current.logged? && author_id == User.current.id
632 633 s << ' assigned-to-me' if User.current.logged? && assigned_to_id == User.current.id
633 634 s
634 635 end
635 636
636 637 # Saves an issue, time_entry, attachments, and a journal from the parameters
637 638 # Returns false if save fails
638 639 def save_issue_with_child_records(params, existing_time_entry=nil)
639 640 Issue.transaction do
640 641 if params[:time_entry] && (params[:time_entry][:hours].present? || params[:time_entry][:comments].present?) && User.current.allowed_to?(:log_time, project)
641 642 @time_entry = existing_time_entry || TimeEntry.new
642 643 @time_entry.project = project
643 644 @time_entry.issue = self
644 645 @time_entry.user = User.current
645 646 @time_entry.spent_on = User.current.today
646 647 @time_entry.attributes = params[:time_entry]
647 648 self.time_entries << @time_entry
648 649 end
649 650
650 651 if valid?
651 652 attachments = Attachment.attach_files(self, params[:attachments])
652 653 # TODO: Rename hook
653 654 Redmine::Hook.call_hook(:controller_issues_edit_before_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
654 655 begin
655 656 if save
656 657 # TODO: Rename hook
657 658 Redmine::Hook.call_hook(:controller_issues_edit_after_save, { :params => params, :issue => self, :time_entry => @time_entry, :journal => @current_journal})
658 659 else
659 660 raise ActiveRecord::Rollback
660 661 end
661 662 rescue ActiveRecord::StaleObjectError
662 663 attachments[:files].each(&:destroy)
663 664 errors.add :base, l(:notice_locking_conflict)
664 665 raise ActiveRecord::Rollback
665 666 end
666 667 end
667 668 end
668 669 end
669 670
670 671 # Unassigns issues from +version+ if it's no longer shared with issue's project
671 672 def self.update_versions_from_sharing_change(version)
672 673 # Update issues assigned to the version
673 674 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
674 675 end
675 676
676 677 # Unassigns issues from versions that are no longer shared
677 678 # after +project+ was moved
678 679 def self.update_versions_from_hierarchy_change(project)
679 680 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
680 681 # Update issues of the moved projects and issues assigned to a version of a moved project
681 682 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
682 683 end
683 684
684 685 def parent_issue_id=(arg)
685 686 parent_issue_id = arg.blank? ? nil : arg.to_i
686 687 if parent_issue_id && @parent_issue = Issue.find_by_id(parent_issue_id)
687 688 @parent_issue.id
688 689 else
689 690 @parent_issue = nil
690 691 nil
691 692 end
692 693 end
693 694
694 695 def parent_issue_id
695 696 if instance_variable_defined? :@parent_issue
696 697 @parent_issue.nil? ? nil : @parent_issue.id
697 698 else
698 699 parent_id
699 700 end
700 701 end
701 702
702 703 # Extracted from the ReportsController.
703 704 def self.by_tracker(project)
704 705 count_and_group_by(:project => project,
705 706 :field => 'tracker_id',
706 707 :joins => Tracker.table_name)
707 708 end
708 709
709 710 def self.by_version(project)
710 711 count_and_group_by(:project => project,
711 712 :field => 'fixed_version_id',
712 713 :joins => Version.table_name)
713 714 end
714 715
715 716 def self.by_priority(project)
716 717 count_and_group_by(:project => project,
717 718 :field => 'priority_id',
718 719 :joins => IssuePriority.table_name)
719 720 end
720 721
721 722 def self.by_category(project)
722 723 count_and_group_by(:project => project,
723 724 :field => 'category_id',
724 725 :joins => IssueCategory.table_name)
725 726 end
726 727
727 728 def self.by_assigned_to(project)
728 729 count_and_group_by(:project => project,
729 730 :field => 'assigned_to_id',
730 731 :joins => User.table_name)
731 732 end
732 733
733 734 def self.by_author(project)
734 735 count_and_group_by(:project => project,
735 736 :field => 'author_id',
736 737 :joins => User.table_name)
737 738 end
738 739
739 740 def self.by_subproject(project)
740 741 ActiveRecord::Base.connection.select_all("select s.id as status_id,
741 742 s.is_closed as closed,
742 743 #{Issue.table_name}.project_id as project_id,
743 744 count(#{Issue.table_name}.id) as total
744 745 from
745 746 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
746 747 where
747 748 #{Issue.table_name}.status_id=s.id
748 749 and #{Issue.table_name}.project_id = #{Project.table_name}.id
749 750 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
750 751 and #{Issue.table_name}.project_id <> #{project.id}
751 752 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
752 753 end
753 754 # End ReportsController extraction
754 755
755 756 # Returns an array of projects that current user can move issues to
756 757 def self.allowed_target_projects_on_move
757 758 projects = []
758 759 if User.current.admin?
759 760 # admin is allowed to move issues to any active (visible) project
760 761 projects = Project.visible.all
761 762 elsif User.current.logged?
762 763 if Role.non_member.allowed_to?(:move_issues)
763 764 projects = Project.visible.all
764 765 else
765 766 User.current.memberships.each {|m| projects << m.project if m.roles.detect {|r| r.allowed_to?(:move_issues)}}
766 767 end
767 768 end
768 769 projects
769 770 end
770 771
771 772 private
772 773
773 774 def update_nested_set_attributes
774 775 if root_id.nil?
775 776 # issue was just created
776 777 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
777 778 set_default_left_and_right
778 779 Issue.update_all("root_id = #{root_id}, lft = #{lft}, rgt = #{rgt}", ["id = ?", id])
779 780 if @parent_issue
780 781 move_to_child_of(@parent_issue)
781 782 end
782 783 reload
783 784 elsif parent_issue_id != parent_id
784 785 former_parent_id = parent_id
785 786 # moving an existing issue
786 787 if @parent_issue && @parent_issue.root_id == root_id
787 788 # inside the same tree
788 789 move_to_child_of(@parent_issue)
789 790 else
790 791 # to another tree
791 792 unless root?
792 793 move_to_right_of(root)
793 794 reload
794 795 end
795 796 old_root_id = root_id
796 797 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
797 798 target_maxright = nested_set_scope.maximum(right_column_name) || 0
798 799 offset = target_maxright + 1 - lft
799 800 Issue.update_all("root_id = #{root_id}, lft = lft + #{offset}, rgt = rgt + #{offset}",
800 801 ["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt])
801 802 self[left_column_name] = lft + offset
802 803 self[right_column_name] = rgt + offset
803 804 if @parent_issue
804 805 move_to_child_of(@parent_issue)
805 806 end
806 807 end
807 808 reload
808 809 # delete invalid relations of all descendants
809 810 self_and_descendants.each do |issue|
810 811 issue.relations.each do |relation|
811 812 relation.destroy unless relation.valid?
812 813 end
813 814 end
814 815 # update former parent
815 816 recalculate_attributes_for(former_parent_id) if former_parent_id
816 817 end
817 818 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
818 819 end
819 820
820 821 def update_parent_attributes
821 822 recalculate_attributes_for(parent_id) if parent_id
822 823 end
823 824
824 825 def recalculate_attributes_for(issue_id)
825 826 if issue_id && p = Issue.find_by_id(issue_id)
826 827 # priority = highest priority of children
827 828 if priority_position = p.children.maximum("#{IssuePriority.table_name}.position", :include => :priority)
828 829 p.priority = IssuePriority.find_by_position(priority_position)
829 830 end
830 831
831 832 # start/due dates = lowest/highest dates of children
832 833 p.start_date = p.children.minimum(:start_date)
833 834 p.due_date = p.children.maximum(:due_date)
834 835 if p.start_date && p.due_date && p.due_date < p.start_date
835 836 p.start_date, p.due_date = p.due_date, p.start_date
836 837 end
837 838
838 839 # done ratio = weighted average ratio of leaves
839 840 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
840 841 leaves_count = p.leaves.count
841 842 if leaves_count > 0
842 843 average = p.leaves.average(:estimated_hours).to_f
843 844 if average == 0
844 845 average = 1
845 846 end
846 847 done = p.leaves.sum("COALESCE(estimated_hours, #{average}) * (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)", :include => :status).to_f
847 848 progress = done / (average * leaves_count)
848 849 p.done_ratio = progress.round
849 850 end
850 851 end
851 852
852 853 # estimate = sum of leaves estimates
853 854 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
854 855 p.estimated_hours = nil if p.estimated_hours == 0.0
855 856
856 857 # ancestors will be recursively updated
857 858 p.save(false)
858 859 end
859 860 end
860 861
861 862 # Update issues so their versions are not pointing to a
862 863 # fixed_version that is not shared with the issue's project
863 864 def self.update_versions(conditions=nil)
864 865 # Only need to update issues with a fixed_version from
865 866 # a different project and that is not systemwide shared
866 867 Issue.all(:conditions => merge_conditions("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
867 868 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
868 869 " AND #{Version.table_name}.sharing <> 'system'",
869 870 conditions),
870 871 :include => [:project, :fixed_version]
871 872 ).each do |issue|
872 873 next if issue.project.nil? || issue.fixed_version.nil?
873 874 unless issue.project.shared_versions.include?(issue.fixed_version)
874 875 issue.init_journal(User.current)
875 876 issue.fixed_version = nil
876 877 issue.save
877 878 end
878 879 end
879 880 end
880 881
881 882 # Callback on attachment deletion
882 883 def attachment_added(obj)
883 884 if @current_journal && !obj.new_record?
884 885 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
885 886 end
886 887 end
887 888
888 889 # Callback on attachment deletion
889 890 def attachment_removed(obj)
890 891 journal = init_journal(User.current)
891 892 journal.details << JournalDetail.new(:property => 'attachment',
892 893 :prop_key => obj.id,
893 894 :old_value => obj.filename)
894 895 journal.save
895 896 end
896 897
897 898 # Default assignment based on category
898 899 def default_assign
899 900 if assigned_to.nil? && category && category.assigned_to
900 901 self.assigned_to = category.assigned_to
901 902 end
902 903 end
903 904
904 905 # Updates start/due dates of following issues
905 906 def reschedule_following_issues
906 907 if start_date_changed? || due_date_changed?
907 908 relations_from.each do |relation|
908 909 relation.set_issue_to_dates
909 910 end
910 911 end
911 912 end
912 913
913 914 # Closes duplicates if the issue is being closed
914 915 def close_duplicates
915 916 if closing?
916 917 duplicates.each do |duplicate|
917 918 # Reload is need in case the duplicate was updated by a previous duplicate
918 919 duplicate.reload
919 920 # Don't re-close it if it's already closed
920 921 next if duplicate.closed?
921 922 # Same user and notes
922 923 if @current_journal
923 924 duplicate.init_journal(@current_journal.user, @current_journal.notes)
924 925 end
925 926 duplicate.update_attribute :status, self.status
926 927 end
927 928 end
928 929 end
929 930
930 931 # Saves the changes in a Journal
931 932 # Called after_save
932 933 def create_journal
933 934 if @current_journal
934 935 # attributes changes
935 936 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
936 937 before = @issue_before_change.send(c)
937 938 after = send(c)
938 939 next if before == after || (before.blank? && after.blank?)
939 940 @current_journal.details << JournalDetail.new(:property => 'attr',
940 941 :prop_key => c,
941 942 :old_value => @issue_before_change.send(c),
942 943 :value => send(c))
943 944 }
944 945 # custom fields changes
945 946 custom_values.each {|c|
946 947 next if (@custom_values_before_change[c.custom_field_id]==c.value ||
947 948 (@custom_values_before_change[c.custom_field_id].blank? && c.value.blank?))
948 949 @current_journal.details << JournalDetail.new(:property => 'cf',
949 950 :prop_key => c.custom_field_id,
950 951 :old_value => @custom_values_before_change[c.custom_field_id],
951 952 :value => c.value)
952 953 }
953 954 @current_journal.save
954 955 # reset current journal
955 956 init_journal @current_journal.user, @current_journal.notes
956 957 end
957 958 end
958 959
959 960 # Query generator for selecting groups of issue counts for a project
960 961 # based on specific criteria
961 962 #
962 963 # Options
963 964 # * project - Project to search in.
964 965 # * field - String. Issue field to key off of in the grouping.
965 966 # * joins - String. The table name to join against.
966 967 def self.count_and_group_by(options)
967 968 project = options.delete(:project)
968 969 select_field = options.delete(:field)
969 970 joins = options.delete(:joins)
970 971
971 972 where = "#{Issue.table_name}.#{select_field}=j.id"
972 973
973 974 ActiveRecord::Base.connection.select_all("select s.id as status_id,
974 975 s.is_closed as closed,
975 976 j.id as #{select_field},
976 977 count(#{Issue.table_name}.id) as total
977 978 from
978 979 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
979 980 where
980 981 #{Issue.table_name}.status_id=s.id
981 982 and #{where}
982 983 and #{Issue.table_name}.project_id=#{Project.table_name}.id
983 984 and #{visible_condition(User.current, :project => project)}
984 985 group by s.id, s.is_closed, j.id")
985 986 end
986 987 end
@@ -1,50 +1,69
1 1 <% labelled_fields_for :issue, @issue do |f| %>
2 2
3 3 <div class="splitcontentleft">
4 <% if @issue.new_record? || @allowed_statuses.any? %>
4 <% if @issue.safe_attribute? 'status_id' %>
5 5 <p><%= f.select :status_id, (@allowed_statuses.collect {|p| [p.name, p.id]}), :required => true %></p>
6 6 <% else %>
7 7 <p><label><%= l(:field_status) %></label> <%= h(@issue.status.name) %></p>
8 8 <% end %>
9 9
10 <% if @issue.safe_attribute? 'priority_id' %>
10 11 <p><%= f.select :priority_id, (@priorities.collect {|p| [p.name, p.id]}), {:required => true}, :disabled => !@issue.leaf? %></p>
12 <% end %>
13
14 <% if @issue.safe_attribute? 'assigned_to_id' %>
11 15 <p><%= f.select :assigned_to_id, principals_options_for_select(@issue.assignable_users, @issue.assigned_to), :include_blank => true %></p>
12 <% unless @project.issue_categories.empty? %>
16 <% end %>
17
18 <% if @issue.safe_attribute?('category_id') && @project.issue_categories.any? %>
13 19 <p><%= f.select :category_id, (@project.issue_categories.collect {|c| [c.name, c.id]}), :include_blank => true %>
14 20 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
15 21 l(:label_issue_category_new),
16 22 'issue_category[name]',
17 23 {:controller => 'issue_categories', :action => 'create', :project_id => @project},
18 24 :title => l(:label_issue_category_new),
19 25 :tabindex => 199) if authorize_for('issue_categories', 'new') %></p>
20 26 <% end %>
21 <% unless @issue.assignable_versions.empty? %>
27
28 <% if @issue.safe_attribute?('fixed_version_id') && @issue.assignable_versions.any? %>
22 29 <p><%= f.select :fixed_version_id, version_options_for_select(@issue.assignable_versions, @issue.fixed_version), :include_blank => true %>
23 30 <%= prompt_to_remote(image_tag('add.png', :style => 'vertical-align: middle;'),
24 31 l(:label_version_new),
25 32 'version[name]',
26 33 {:controller => 'versions', :action => 'create', :project_id => @project},
27 34 :title => l(:label_version_new),
28 35 :tabindex => 200) if authorize_for('versions', 'new') %>
29 36 </p>
30 37 <% end %>
31 38 </div>
32 39
33 40 <div class="splitcontentright">
34 41 <% if @issue.safe_attribute? 'parent_issue_id' %>
35 42 <p id="parent_issue"><%= f.text_field :parent_issue_id, :size => 10 %></p>
36 43 <div id="parent_issue_candidates" class="autocomplete"></div>
37 44 <%= javascript_tag "observeParentIssueField('#{auto_complete_issues_path(:id => @issue, :project_id => @project) }')" %>
38 45 <% end %>
46
47 <% if @issue.safe_attribute? 'start_date' %>
39 48 <p><%= f.text_field :start_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_start_date') if @issue.leaf? %></p>
49 <% end %>
50
51 <% if @issue.safe_attribute? 'due_date' %>
40 52 <p><%= f.text_field :due_date, :size => 10, :disabled => !@issue.leaf? %><%= calendar_for('issue_due_date') if @issue.leaf? %></p>
53 <% end %>
54
55 <% if @issue.safe_attribute? 'estimated_hours' %>
41 56 <p><%= f.text_field :estimated_hours, :size => 3, :disabled => !@issue.leaf? %> <%= l(:field_hours) %></p>
42 <% if @issue.leaf? && Issue.use_field_for_done_ratio? %>
57 <% end %>
58
59 <% if @issue.safe_attribute?('done_ratio') && @issue.leaf? && Issue.use_field_for_done_ratio? %>
43 60 <p><%= f.select :done_ratio, ((0..10).to_a.collect {|r| ["#{r*10} %", r*10] }) %></p>
44 61 <% end %>
45 62 </div>
46 63
47 64 <div style="clear:both;"> </div>
65 <% if @issue.safe_attribute? 'custom_field_values' %>
48 66 <%= render :partial => 'issues/form_custom_fields' %>
67 <% end %>
49 68
50 69 <% end %>
@@ -1,46 +1,46
1 1 <% labelled_form_for @issue, :html => {:id => 'issue-form', :multipart => true} do |f| %>
2 2 <%= error_messages_for 'issue', 'time_entry' %>
3 3 <div class="box">
4 4 <% if @edit_allowed || !@allowed_statuses.empty? %>
5 5 <fieldset class="tabular"><legend><%= l(:label_change_properties) %></legend>
6 <%= render :partial => (@edit_allowed ? 'form' : 'form_update'), :locals => {:f => f} %>
6 <%= render :partial => 'form', :locals => {:f => f} %>
7 7 </fieldset>
8 8 <% end %>
9 9 <% if User.current.allowed_to?(:log_time, @project) %>
10 10 <fieldset class="tabular"><legend><%= l(:button_log_time) %></legend>
11 11 <% labelled_fields_for :time_entry, @time_entry do |time_entry| %>
12 12 <div class="splitcontentleft">
13 13 <p><%= time_entry.text_field :hours, :size => 6, :label => :label_spent_time %> <%= l(:field_hours) %></p>
14 14 </div>
15 15 <div class="splitcontentright">
16 16 <p><%= time_entry.select :activity_id, activity_collection_for_select_options %></p>
17 17 </div>
18 18 <p><%= time_entry.text_field :comments, :size => 60 %></p>
19 19 <% @time_entry.custom_field_values.each do |value| %>
20 20 <p><%= custom_field_tag_with_label :time_entry, value %></p>
21 21 <% end %>
22 22 <% end %>
23 23 </fieldset>
24 24 <% end %>
25 25
26 26 <fieldset><legend><%= l(:field_notes) %></legend>
27 27 <%= text_area_tag 'notes', @notes, :cols => 60, :rows => 10, :class => 'wiki-edit' %>
28 28 <%= wikitoolbar_for 'notes' %>
29 29 <%= call_hook(:view_issues_edit_notes_bottom, { :issue => @issue, :notes => @notes, :form => f }) %>
30 30
31 31 <p><%=l(:label_attachment_plural)%><br /><%= render :partial => 'attachments/form' %></p>
32 32 </fieldset>
33 33 </div>
34 34
35 35 <%= f.hidden_field :lock_version %>
36 36 <%= submit_tag l(:button_submit) %>
37 37 <%= link_to_remote l(:label_preview),
38 38 { :url => preview_issue_path(:project_id => @project, :id => @issue),
39 39 :method => 'post',
40 40 :update => 'preview',
41 41 :with => 'Form.serialize("issue-form")',
42 42 :complete => "Element.scrollTo('preview')"
43 43 }, :accesskey => accesskey(:preview) %>
44 44 <% end %>
45 45
46 46 <div id="preview" class="wiki"></div>
@@ -1,34 +1,41
1 1 <%= call_hook(:view_issues_form_details_top, { :issue => @issue, :form => f }) %>
2 2
3 3 <% if @issue.safe_attribute? 'is_private' %>
4 4 <p style="float:right; margin-right:1em;">
5 5 <label class="inline" for="issue_is_private" id="issue_is_private_label"><%= f.check_box :is_private, :no_label => true %> <%= l(:field_is_private) %></label>
6 6 </p>
7 7 <% end %>
8
9 <% if @issue.safe_attribute? 'tracker_id' %>
8 10 <p><%= f.select :tracker_id, @project.trackers.collect {|t| [t.name, t.id]}, :required => true %></p>
9 11 <%= observe_field :issue_tracker_id, :url => project_issue_form_path(@project, :id => @issue),
10 12 :update => :attributes,
11 13 :with => "Form.serialize('issue-form')" %>
14 <% end %>
12 15
16 <% if @issue.safe_attribute? 'subject' %>
13 17 <p><%= f.text_field :subject, :size => 80, :required => true %></p>
18 <% end %>
19
20 <% if @issue.safe_attribute? 'description' %>
14 21 <p>
15 22 <label><%= l(:field_description) %></label>
16 23 <%= link_to_function image_tag('edit.png'),
17 24 'Element.hide(this); Effect.toggle("issue_description_and_toolbar", "appear", {duration:0.3})' unless @issue.new_record? %>
18 25 <% content_tag 'span', :id => "issue_description_and_toolbar", :style => (@issue.new_record? ? nil : 'display:none') do %>
19 26 <%= f.text_area :description,
20 27 :cols => 60,
21 28 :rows => (@issue.description.blank? ? 10 : [[10, @issue.description.length / 50].max, 100].min),
22 29 :accesskey => accesskey(:edit),
23 30 :class => 'wiki-edit',
24 31 :no_label => true %>
25 32 <% end %>
26 33 </p>
34 <%= wikitoolbar_for 'issue_description' %>
35 <% end %>
27 36
28 37 <div id="attributes" class="attributes">
29 38 <%= render :partial => 'issues/attributes' %>
30 39 </div>
31 40
32 41 <%= call_hook(:view_issues_form_details_bottom, { :issue => @issue, :form => f }) %>
33
34 <%= wikitoolbar_for 'issue_description' %>
@@ -1,2065 +1,2191
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../test_helper', __FILE__)
19 19 require 'issues_controller'
20 20
21 21 class IssuesControllerTest < ActionController::TestCase
22 22 fixtures :projects,
23 23 :users,
24 24 :roles,
25 25 :members,
26 26 :member_roles,
27 27 :issues,
28 28 :issue_statuses,
29 29 :versions,
30 30 :trackers,
31 31 :projects_trackers,
32 32 :issue_categories,
33 33 :enabled_modules,
34 34 :enumerations,
35 35 :attachments,
36 36 :workflows,
37 37 :custom_fields,
38 38 :custom_values,
39 39 :custom_fields_projects,
40 40 :custom_fields_trackers,
41 41 :time_entries,
42 42 :journals,
43 43 :journal_details,
44 44 :queries
45 45
46 46 include Redmine::I18n
47 47
48 48 def setup
49 49 @controller = IssuesController.new
50 50 @request = ActionController::TestRequest.new
51 51 @response = ActionController::TestResponse.new
52 52 User.current = nil
53 53 end
54 54
55 55 def test_index
56 56 Setting.default_language = 'en'
57 57
58 58 get :index
59 59 assert_response :success
60 60 assert_template 'index'
61 61 assert_not_nil assigns(:issues)
62 62 assert_nil assigns(:project)
63 63 assert_tag :tag => 'a', :content => /Can't print recipes/
64 64 assert_tag :tag => 'a', :content => /Subproject issue/
65 65 # private projects hidden
66 66 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
67 67 assert_no_tag :tag => 'a', :content => /Issue on project 2/
68 68 # project column
69 69 assert_tag :tag => 'th', :content => /Project/
70 70 end
71 71
72 72 def test_index_should_not_list_issues_when_module_disabled
73 73 EnabledModule.delete_all("name = 'issue_tracking' AND project_id = 1")
74 74 get :index
75 75 assert_response :success
76 76 assert_template 'index'
77 77 assert_not_nil assigns(:issues)
78 78 assert_nil assigns(:project)
79 79 assert_no_tag :tag => 'a', :content => /Can't print recipes/
80 80 assert_tag :tag => 'a', :content => /Subproject issue/
81 81 end
82 82
83 83 def test_index_should_list_visible_issues_only
84 84 get :index, :per_page => 100
85 85 assert_response :success
86 86 assert_not_nil assigns(:issues)
87 87 assert_nil assigns(:issues).detect {|issue| !issue.visible?}
88 88 end
89 89
90 90 def test_index_with_project
91 91 Setting.display_subprojects_issues = 0
92 92 get :index, :project_id => 1
93 93 assert_response :success
94 94 assert_template 'index'
95 95 assert_not_nil assigns(:issues)
96 96 assert_tag :tag => 'a', :content => /Can't print recipes/
97 97 assert_no_tag :tag => 'a', :content => /Subproject issue/
98 98 end
99 99
100 100 def test_index_with_project_and_subprojects
101 101 Setting.display_subprojects_issues = 1
102 102 get :index, :project_id => 1
103 103 assert_response :success
104 104 assert_template 'index'
105 105 assert_not_nil assigns(:issues)
106 106 assert_tag :tag => 'a', :content => /Can't print recipes/
107 107 assert_tag :tag => 'a', :content => /Subproject issue/
108 108 assert_no_tag :tag => 'a', :content => /Issue of a private subproject/
109 109 end
110 110
111 111 def test_index_with_project_and_subprojects_should_show_private_subprojects
112 112 @request.session[:user_id] = 2
113 113 Setting.display_subprojects_issues = 1
114 114 get :index, :project_id => 1
115 115 assert_response :success
116 116 assert_template 'index'
117 117 assert_not_nil assigns(:issues)
118 118 assert_tag :tag => 'a', :content => /Can't print recipes/
119 119 assert_tag :tag => 'a', :content => /Subproject issue/
120 120 assert_tag :tag => 'a', :content => /Issue of a private subproject/
121 121 end
122 122
123 123 def test_index_with_project_and_default_filter
124 124 get :index, :project_id => 1, :set_filter => 1
125 125 assert_response :success
126 126 assert_template 'index'
127 127 assert_not_nil assigns(:issues)
128 128
129 129 query = assigns(:query)
130 130 assert_not_nil query
131 131 # default filter
132 132 assert_equal({'status_id' => {:operator => 'o', :values => ['']}}, query.filters)
133 133 end
134 134
135 135 def test_index_with_project_and_filter
136 136 get :index, :project_id => 1, :set_filter => 1,
137 137 :f => ['tracker_id'],
138 138 :op => {'tracker_id' => '='},
139 139 :v => {'tracker_id' => ['1']}
140 140 assert_response :success
141 141 assert_template 'index'
142 142 assert_not_nil assigns(:issues)
143 143
144 144 query = assigns(:query)
145 145 assert_not_nil query
146 146 assert_equal({'tracker_id' => {:operator => '=', :values => ['1']}}, query.filters)
147 147 end
148 148
149 149 def test_index_with_short_filters
150 150
151 151 to_test = {
152 152 'status_id' => {
153 153 'o' => { :op => 'o', :values => [''] },
154 154 'c' => { :op => 'c', :values => [''] },
155 155 '7' => { :op => '=', :values => ['7'] },
156 156 '7|3|4' => { :op => '=', :values => ['7', '3', '4'] },
157 157 '=7' => { :op => '=', :values => ['7'] },
158 158 '!3' => { :op => '!', :values => ['3'] },
159 159 '!7|3|4' => { :op => '!', :values => ['7', '3', '4'] }},
160 160 'subject' => {
161 161 'This is a subject' => { :op => '=', :values => ['This is a subject'] },
162 162 'o' => { :op => '=', :values => ['o'] },
163 163 '~This is part of a subject' => { :op => '~', :values => ['This is part of a subject'] },
164 164 '!~This is part of a subject' => { :op => '!~', :values => ['This is part of a subject'] }},
165 165 'tracker_id' => {
166 166 '3' => { :op => '=', :values => ['3'] },
167 167 '=3' => { :op => '=', :values => ['3'] }},
168 168 'start_date' => {
169 169 '2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
170 170 '=2011-10-12' => { :op => '=', :values => ['2011-10-12'] },
171 171 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
172 172 '<=2011-10-12' => { :op => '<=', :values => ['2011-10-12'] },
173 173 '><2011-10-01|2011-10-30' => { :op => '><', :values => ['2011-10-01', '2011-10-30'] },
174 174 '<t+2' => { :op => '<t+', :values => ['2'] },
175 175 '>t+2' => { :op => '>t+', :values => ['2'] },
176 176 't+2' => { :op => 't+', :values => ['2'] },
177 177 't' => { :op => 't', :values => [''] },
178 178 'w' => { :op => 'w', :values => [''] },
179 179 '>t-2' => { :op => '>t-', :values => ['2'] },
180 180 '<t-2' => { :op => '<t-', :values => ['2'] },
181 181 't-2' => { :op => 't-', :values => ['2'] }},
182 182 'created_on' => {
183 183 '>=2011-10-12' => { :op => '>=', :values => ['2011-10-12'] },
184 184 '<t+2' => { :op => '=', :values => ['<t+2'] },
185 185 '>t+2' => { :op => '=', :values => ['>t+2'] },
186 186 't+2' => { :op => 't', :values => ['+2'] }},
187 187 'cf_1' => {
188 188 'c' => { :op => '=', :values => ['c'] },
189 189 '!c' => { :op => '!', :values => ['c'] },
190 190 '!*' => { :op => '!*', :values => [''] },
191 191 '*' => { :op => '*', :values => [''] }},
192 192 'estimated_hours' => {
193 193 '=13.4' => { :op => '=', :values => ['13.4'] },
194 194 '>=45' => { :op => '>=', :values => ['45'] },
195 195 '<=125' => { :op => '<=', :values => ['125'] },
196 196 '><10.5|20.5' => { :op => '><', :values => ['10.5', '20.5'] },
197 197 '!*' => { :op => '!*', :values => [''] },
198 198 '*' => { :op => '*', :values => [''] }}
199 199 }
200 200
201 201 default_filter = { 'status_id' => {:operator => 'o', :values => [''] }}
202 202
203 203 to_test.each do |field, expression_and_expected|
204 204 expression_and_expected.each do |filter_expression, expected|
205 205
206 206 get :index, :set_filter => 1, field => filter_expression
207 207
208 208 assert_response :success
209 209 assert_template 'index'
210 210 assert_not_nil assigns(:issues)
211 211
212 212 query = assigns(:query)
213 213 assert_not_nil query
214 214 assert query.has_filter?(field)
215 215 assert_equal(default_filter.merge({field => {:operator => expected[:op], :values => expected[:values]}}), query.filters)
216 216 end
217 217 end
218 218
219 219 end
220 220
221 221 def test_index_with_project_and_empty_filters
222 222 get :index, :project_id => 1, :set_filter => 1, :fields => ['']
223 223 assert_response :success
224 224 assert_template 'index'
225 225 assert_not_nil assigns(:issues)
226 226
227 227 query = assigns(:query)
228 228 assert_not_nil query
229 229 # no filter
230 230 assert_equal({}, query.filters)
231 231 end
232 232
233 233 def test_index_with_query
234 234 get :index, :project_id => 1, :query_id => 5
235 235 assert_response :success
236 236 assert_template 'index'
237 237 assert_not_nil assigns(:issues)
238 238 assert_nil assigns(:issue_count_by_group)
239 239 end
240 240
241 241 def test_index_with_query_grouped_by_tracker
242 242 get :index, :project_id => 1, :query_id => 6
243 243 assert_response :success
244 244 assert_template 'index'
245 245 assert_not_nil assigns(:issues)
246 246 assert_not_nil assigns(:issue_count_by_group)
247 247 end
248 248
249 249 def test_index_with_query_grouped_by_list_custom_field
250 250 get :index, :project_id => 1, :query_id => 9
251 251 assert_response :success
252 252 assert_template 'index'
253 253 assert_not_nil assigns(:issues)
254 254 assert_not_nil assigns(:issue_count_by_group)
255 255 end
256 256
257 257 def test_index_with_query_id_and_project_id_should_set_session_query
258 258 get :index, :project_id => 1, :query_id => 4
259 259 assert_response :success
260 260 assert_kind_of Hash, session[:query]
261 261 assert_equal 4, session[:query][:id]
262 262 assert_equal 1, session[:query][:project_id]
263 263 end
264 264
265 265 def test_index_with_cross_project_query_in_session_should_show_project_issues
266 266 q = Query.create!(:name => "test", :user_id => 2, :is_public => false, :project => nil)
267 267 @request.session[:query] = {:id => q.id, :project_id => 1}
268 268
269 269 with_settings :display_subprojects_issues => '0' do
270 270 get :index, :project_id => 1
271 271 end
272 272 assert_response :success
273 273 assert_not_nil assigns(:query)
274 274 assert_equal q.id, assigns(:query).id
275 275 assert_equal 1, assigns(:query).project_id
276 276 assert_equal [1], assigns(:issues).map(&:project_id).uniq
277 277 end
278 278
279 279 def test_private_query_should_not_be_available_to_other_users
280 280 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
281 281 @request.session[:user_id] = 3
282 282
283 283 get :index, :query_id => q.id
284 284 assert_response 403
285 285 end
286 286
287 287 def test_private_query_should_be_available_to_its_user
288 288 q = Query.create!(:name => "private", :user => User.find(2), :is_public => false, :project => nil)
289 289 @request.session[:user_id] = 2
290 290
291 291 get :index, :query_id => q.id
292 292 assert_response :success
293 293 end
294 294
295 295 def test_public_query_should_be_available_to_other_users
296 296 q = Query.create!(:name => "private", :user => User.find(2), :is_public => true, :project => nil)
297 297 @request.session[:user_id] = 3
298 298
299 299 get :index, :query_id => q.id
300 300 assert_response :success
301 301 end
302 302
303 303 def test_index_csv
304 304 get :index, :format => 'csv'
305 305 assert_response :success
306 306 assert_not_nil assigns(:issues)
307 307 assert_equal 'text/csv', @response.content_type
308 308 assert @response.body.starts_with?("#,")
309 309 lines = @response.body.chomp.split("\n")
310 310 assert_equal assigns(:query).columns.size + 1, lines[0].split(',').size
311 311 end
312 312
313 313 def test_index_csv_with_project
314 314 get :index, :project_id => 1, :format => 'csv'
315 315 assert_response :success
316 316 assert_not_nil assigns(:issues)
317 317 assert_equal 'text/csv', @response.content_type
318 318 end
319 319
320 320 def test_index_csv_with_description
321 321 get :index, :format => 'csv', :description => '1'
322 322 assert_response :success
323 323 assert_not_nil assigns(:issues)
324 324 assert_equal 'text/csv', @response.content_type
325 325 assert @response.body.starts_with?("#,")
326 326 lines = @response.body.chomp.split("\n")
327 327 assert_equal assigns(:query).columns.size + 2, lines[0].split(',').size
328 328 end
329 329
330 330 def test_index_csv_with_all_columns
331 331 get :index, :format => 'csv', :columns => 'all'
332 332 assert_response :success
333 333 assert_not_nil assigns(:issues)
334 334 assert_equal 'text/csv', @response.content_type
335 335 assert @response.body.starts_with?("#,")
336 336 lines = @response.body.chomp.split("\n")
337 337 assert_equal assigns(:query).available_columns.size + 1, lines[0].split(',').size
338 338 end
339 339
340 340 def test_index_csv_big_5
341 341 with_settings :default_language => "zh-TW" do
342 342 str_utf8 = "\xe4\xb8\x80\xe6\x9c\x88"
343 343 str_big5 = "\xa4@\xa4\xeb"
344 344 if str_utf8.respond_to?(:force_encoding)
345 345 str_utf8.force_encoding('UTF-8')
346 346 str_big5.force_encoding('Big5')
347 347 end
348 348 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
349 349 :status_id => 1, :priority => IssuePriority.all.first,
350 350 :subject => str_utf8)
351 351 assert issue.save
352 352
353 353 get :index, :project_id => 1,
354 354 :f => ['subject'],
355 355 :op => '=', :values => [str_utf8],
356 356 :format => 'csv'
357 357 assert_equal 'text/csv', @response.content_type
358 358 lines = @response.body.chomp.split("\n")
359 359 s1 = "\xaa\xac\xbaA"
360 360 if str_utf8.respond_to?(:force_encoding)
361 361 s1.force_encoding('Big5')
362 362 end
363 363 assert lines[0].include?(s1)
364 364 assert lines[1].include?(str_big5)
365 365 end
366 366 end
367 367
368 368 def test_index_csv_cannot_convert_should_be_replaced_big_5
369 369 with_settings :default_language => "zh-TW" do
370 370 str_utf8 = "\xe4\xbb\xa5\xe5\x86\x85"
371 371 if str_utf8.respond_to?(:force_encoding)
372 372 str_utf8.force_encoding('UTF-8')
373 373 end
374 374 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
375 375 :status_id => 1, :priority => IssuePriority.all.first,
376 376 :subject => str_utf8)
377 377 assert issue.save
378 378
379 379 get :index, :project_id => 1,
380 380 :f => ['subject'],
381 381 :op => '=', :values => [str_utf8],
382 382 :c => ['status', 'subject'],
383 383 :format => 'csv',
384 384 :set_filter => 1
385 385 assert_equal 'text/csv', @response.content_type
386 386 lines = @response.body.chomp.split("\n")
387 387 s1 = "\xaa\xac\xbaA" # status
388 388 if str_utf8.respond_to?(:force_encoding)
389 389 s1.force_encoding('Big5')
390 390 end
391 391 assert lines[0].include?(s1)
392 392 s2 = lines[1].split(",")[2]
393 393 if s1.respond_to?(:force_encoding)
394 394 s3 = "\xa5H?" # subject
395 395 s3.force_encoding('Big5')
396 396 assert_equal s3, s2
397 397 elsif RUBY_PLATFORM == 'java'
398 398 assert_equal "??", s2
399 399 else
400 400 assert_equal "\xa5H???", s2
401 401 end
402 402 end
403 403 end
404 404
405 405 def test_index_csv_tw
406 406 with_settings :default_language => "zh-TW" do
407 407 str1 = "test_index_csv_tw"
408 408 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
409 409 :status_id => 1, :priority => IssuePriority.all.first,
410 410 :subject => str1, :estimated_hours => '1234.5')
411 411 assert issue.save
412 412 assert_equal 1234.5, issue.estimated_hours
413 413
414 414 get :index, :project_id => 1,
415 415 :f => ['subject'],
416 416 :op => '=', :values => [str1],
417 417 :c => ['estimated_hours', 'subject'],
418 418 :format => 'csv',
419 419 :set_filter => 1
420 420 assert_equal 'text/csv', @response.content_type
421 421 lines = @response.body.chomp.split("\n")
422 422 assert_equal "#{issue.id},1234.5,#{str1}", lines[1]
423 423
424 424 str_tw = "Traditional Chinese (\xe7\xb9\x81\xe9\xab\x94\xe4\xb8\xad\xe6\x96\x87)"
425 425 if str_tw.respond_to?(:force_encoding)
426 426 str_tw.force_encoding('UTF-8')
427 427 end
428 428 assert_equal str_tw, l(:general_lang_name)
429 429 assert_equal ',', l(:general_csv_separator)
430 430 assert_equal '.', l(:general_csv_decimal_separator)
431 431 end
432 432 end
433 433
434 434 def test_index_csv_fr
435 435 with_settings :default_language => "fr" do
436 436 str1 = "test_index_csv_fr"
437 437 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
438 438 :status_id => 1, :priority => IssuePriority.all.first,
439 439 :subject => str1, :estimated_hours => '1234.5')
440 440 assert issue.save
441 441 assert_equal 1234.5, issue.estimated_hours
442 442
443 443 get :index, :project_id => 1,
444 444 :f => ['subject'],
445 445 :op => '=', :values => [str1],
446 446 :c => ['estimated_hours', 'subject'],
447 447 :format => 'csv',
448 448 :set_filter => 1
449 449 assert_equal 'text/csv', @response.content_type
450 450 lines = @response.body.chomp.split("\n")
451 451 assert_equal "#{issue.id};1234,5;#{str1}", lines[1]
452 452
453 453 str_fr = "Fran\xc3\xa7ais"
454 454 if str_fr.respond_to?(:force_encoding)
455 455 str_fr.force_encoding('UTF-8')
456 456 end
457 457 assert_equal str_fr, l(:general_lang_name)
458 458 assert_equal ';', l(:general_csv_separator)
459 459 assert_equal ',', l(:general_csv_decimal_separator)
460 460 end
461 461 end
462 462
463 463 def test_index_pdf
464 464 ["en", "zh", "zh-TW", "ja", "ko"].each do |lang|
465 465 with_settings :default_language => lang do
466 466
467 467 get :index
468 468 assert_response :success
469 469 assert_template 'index'
470 470
471 471 if lang == "ja"
472 472 if RUBY_PLATFORM != 'java'
473 473 assert_equal "CP932", l(:general_pdf_encoding)
474 474 end
475 475 if RUBY_PLATFORM == 'java' && l(:general_pdf_encoding) == "CP932"
476 476 next
477 477 end
478 478 end
479 479
480 480 get :index, :format => 'pdf'
481 481 assert_response :success
482 482 assert_not_nil assigns(:issues)
483 483 assert_equal 'application/pdf', @response.content_type
484 484
485 485 get :index, :project_id => 1, :format => 'pdf'
486 486 assert_response :success
487 487 assert_not_nil assigns(:issues)
488 488 assert_equal 'application/pdf', @response.content_type
489 489
490 490 get :index, :project_id => 1, :query_id => 6, :format => 'pdf'
491 491 assert_response :success
492 492 assert_not_nil assigns(:issues)
493 493 assert_equal 'application/pdf', @response.content_type
494 494 end
495 495 end
496 496 end
497 497
498 498 def test_index_pdf_with_query_grouped_by_list_custom_field
499 499 get :index, :project_id => 1, :query_id => 9, :format => 'pdf'
500 500 assert_response :success
501 501 assert_not_nil assigns(:issues)
502 502 assert_not_nil assigns(:issue_count_by_group)
503 503 assert_equal 'application/pdf', @response.content_type
504 504 end
505 505
506 506 def test_index_sort
507 507 get :index, :sort => 'tracker,id:desc'
508 508 assert_response :success
509 509
510 510 sort_params = @request.session['issues_index_sort']
511 511 assert sort_params.is_a?(String)
512 512 assert_equal 'tracker,id:desc', sort_params
513 513
514 514 issues = assigns(:issues)
515 515 assert_not_nil issues
516 516 assert !issues.empty?
517 517 assert_equal issues.sort {|a,b| a.tracker == b.tracker ? b.id <=> a.id : a.tracker <=> b.tracker }.collect(&:id), issues.collect(&:id)
518 518 end
519 519
520 520 def test_index_sort_by_field_not_included_in_columns
521 521 Setting.issue_list_default_columns = %w(subject author)
522 522 get :index, :sort => 'tracker'
523 523 end
524 524
525 525 def test_index_sort_by_assigned_to
526 526 get :index, :sort => 'assigned_to'
527 527 assert_response :success
528 528 assignees = assigns(:issues).collect(&:assigned_to).compact
529 529 assert_equal assignees.sort, assignees
530 530 end
531 531
532 532 def test_index_sort_by_assigned_to_desc
533 533 get :index, :sort => 'assigned_to:desc'
534 534 assert_response :success
535 535 assignees = assigns(:issues).collect(&:assigned_to).compact
536 536 assert_equal assignees.sort.reverse, assignees
537 537 end
538 538
539 539 def test_index_group_by_assigned_to
540 540 get :index, :group_by => 'assigned_to', :sort => 'priority'
541 541 assert_response :success
542 542 end
543 543
544 544 def test_index_sort_by_author
545 545 get :index, :sort => 'author'
546 546 assert_response :success
547 547 authors = assigns(:issues).collect(&:author)
548 548 assert_equal authors.sort, authors
549 549 end
550 550
551 551 def test_index_sort_by_author_desc
552 552 get :index, :sort => 'author:desc'
553 553 assert_response :success
554 554 authors = assigns(:issues).collect(&:author)
555 555 assert_equal authors.sort.reverse, authors
556 556 end
557 557
558 558 def test_index_group_by_author
559 559 get :index, :group_by => 'author', :sort => 'priority'
560 560 assert_response :success
561 561 end
562 562
563 563 def test_index_sort_by_spent_hours
564 564 get :index, :sort => 'spent_hours:desc'
565 565 assert_response :success
566 566 hours = assigns(:issues).collect(&:spent_hours)
567 567 assert_equal hours.sort.reverse, hours
568 568 end
569 569
570 570 def test_index_with_columns
571 571 columns = ['tracker', 'subject', 'assigned_to']
572 572 get :index, :set_filter => 1, :c => columns
573 573 assert_response :success
574 574
575 575 # query should use specified columns
576 576 query = assigns(:query)
577 577 assert_kind_of Query, query
578 578 assert_equal columns, query.column_names.map(&:to_s)
579 579
580 580 # columns should be stored in session
581 581 assert_kind_of Hash, session[:query]
582 582 assert_kind_of Array, session[:query][:column_names]
583 583 assert_equal columns, session[:query][:column_names].map(&:to_s)
584 584
585 585 # ensure only these columns are kept in the selected columns list
586 586 assert_tag :tag => 'select', :attributes => { :id => 'selected_columns' },
587 587 :children => { :count => 3 }
588 588 assert_no_tag :tag => 'option', :attributes => { :value => 'project' },
589 589 :parent => { :tag => 'select', :attributes => { :id => "selected_columns" } }
590 590 end
591 591
592 592 def test_index_without_project_should_implicitly_add_project_column_to_default_columns
593 593 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
594 594 get :index, :set_filter => 1
595 595
596 596 # query should use specified columns
597 597 query = assigns(:query)
598 598 assert_kind_of Query, query
599 599 assert_equal [:project, :tracker, :subject, :assigned_to], query.columns.map(&:name)
600 600 end
601 601
602 602 def test_index_without_project_and_explicit_default_columns_should_not_add_project_column
603 603 Setting.issue_list_default_columns = ['tracker', 'subject', 'assigned_to']
604 604 columns = ['tracker', 'subject', 'assigned_to']
605 605 get :index, :set_filter => 1, :c => columns
606 606
607 607 # query should use specified columns
608 608 query = assigns(:query)
609 609 assert_kind_of Query, query
610 610 assert_equal columns.map(&:to_sym), query.columns.map(&:name)
611 611 end
612 612
613 613 def test_index_with_custom_field_column
614 614 columns = %w(tracker subject cf_2)
615 615 get :index, :set_filter => 1, :c => columns
616 616 assert_response :success
617 617
618 618 # query should use specified columns
619 619 query = assigns(:query)
620 620 assert_kind_of Query, query
621 621 assert_equal columns, query.column_names.map(&:to_s)
622 622
623 623 assert_tag :td,
624 624 :attributes => {:class => 'cf_2 string'},
625 625 :ancestor => {:tag => 'table', :attributes => {:class => /issues/}}
626 626 end
627 627
628 628 def test_index_with_date_column
629 629 Issue.find(1).update_attribute :start_date, '1987-08-24'
630 630
631 631 with_settings :date_format => '%d/%m/%Y' do
632 632 get :index, :set_filter => 1, :c => %w(start_date)
633 633 assert_tag 'td', :attributes => {:class => /start_date/}, :content => '24/08/1987'
634 634 end
635 635 end
636 636
637 637 def test_index_with_done_ratio
638 638 Issue.find(1).update_attribute :done_ratio, 40
639 639
640 640 get :index, :set_filter => 1, :c => %w(done_ratio)
641 641 assert_tag 'td', :attributes => {:class => /done_ratio/},
642 642 :child => {:tag => 'table', :attributes => {:class => 'progress'},
643 643 :descendant => {:tag => 'td', :attributes => {:class => 'closed', :style => 'width: 40%;'}}
644 644 }
645 645 end
646 646
647 647 def test_index_with_spent_hours_column
648 648 get :index, :set_filter => 1, :c => %w(subject spent_hours)
649 649
650 650 assert_tag 'tr', :attributes => {:id => 'issue-3'},
651 651 :child => {
652 652 :tag => 'td', :attributes => {:class => /spent_hours/}, :content => '1.00'
653 653 }
654 654 end
655 655
656 656 def test_index_should_not_show_spent_hours_column_without_permission
657 657 Role.anonymous.remove_permission! :view_time_entries
658 658 get :index, :set_filter => 1, :c => %w(subject spent_hours)
659 659
660 660 assert_no_tag 'td', :attributes => {:class => /spent_hours/}
661 661 end
662 662
663 663 def test_index_with_fixed_version
664 664 get :index, :set_filter => 1, :c => %w(fixed_version)
665 665 assert_tag 'td', :attributes => {:class => /fixed_version/},
666 666 :child => {:tag => 'a', :content => '1.0', :attributes => {:href => '/versions/2'}}
667 667 end
668 668
669 669 def test_index_send_html_if_query_is_invalid
670 670 get :index, :f => ['start_date'], :op => {:start_date => '='}
671 671 assert_equal 'text/html', @response.content_type
672 672 assert_template 'index'
673 673 end
674 674
675 675 def test_index_send_nothing_if_query_is_invalid
676 676 get :index, :f => ['start_date'], :op => {:start_date => '='}, :format => 'csv'
677 677 assert_equal 'text/csv', @response.content_type
678 678 assert @response.body.blank?
679 679 end
680 680
681 681 def test_show_by_anonymous
682 682 get :show, :id => 1
683 683 assert_response :success
684 684 assert_template 'show'
685 685 assert_not_nil assigns(:issue)
686 686 assert_equal Issue.find(1), assigns(:issue)
687 687
688 688 # anonymous role is allowed to add a note
689 689 assert_tag :tag => 'form',
690 690 :descendant => { :tag => 'fieldset',
691 691 :child => { :tag => 'legend',
692 692 :content => /Notes/ } }
693 693 assert_tag :tag => 'title',
694 694 :content => "Bug #1: Can't print recipes - eCookbook - Redmine"
695 695 end
696 696
697 697 def test_show_by_manager
698 698 @request.session[:user_id] = 2
699 699 get :show, :id => 1
700 700 assert_response :success
701 701
702 702 assert_tag :tag => 'a',
703 703 :content => /Quote/
704 704
705 705 assert_tag :tag => 'form',
706 706 :descendant => { :tag => 'fieldset',
707 707 :child => { :tag => 'legend',
708 708 :content => /Change properties/ } },
709 709 :descendant => { :tag => 'fieldset',
710 710 :child => { :tag => 'legend',
711 711 :content => /Log time/ } },
712 712 :descendant => { :tag => 'fieldset',
713 713 :child => { :tag => 'legend',
714 714 :content => /Notes/ } }
715 715 end
716 716
717 def test_show_should_display_update_form
718 @request.session[:user_id] = 2
719 get :show, :id => 1
720 assert_response :success
721
722 assert_tag 'form', :attributes => {:id => 'issue-form'}
723 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
724 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
725 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
726 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
727 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
728 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
729 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
730 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
731 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
732 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
733 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
734 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
735 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
736 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
737 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
738 assert_tag 'textarea', :attributes => {:name => 'notes'}
739 end
740
741 def test_show_should_display_update_form_with_minimal_permissions
742 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
743 Workflow.delete_all :role_id => 1
744
745 @request.session[:user_id] = 2
746 get :show, :id => 1
747 assert_response :success
748
749 assert_tag 'form', :attributes => {:id => 'issue-form'}
750 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
751 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
752 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
753 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
754 assert_no_tag 'select', :attributes => {:name => 'issue[status_id]'}
755 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
756 assert_no_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
757 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
758 assert_no_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
759 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
760 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
761 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
762 assert_no_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
763 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
764 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
765 assert_tag 'textarea', :attributes => {:name => 'notes'}
766 end
767
768 def test_show_should_display_update_form_with_workflow_permissions
769 Role.find(1).update_attribute :permissions, [:view_issues, :add_issue_notes]
770
771 @request.session[:user_id] = 2
772 get :show, :id => 1
773 assert_response :success
774
775 assert_tag 'form', :attributes => {:id => 'issue-form'}
776 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
777 assert_no_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
778 assert_no_tag 'input', :attributes => {:name => 'issue[subject]'}
779 assert_no_tag 'textarea', :attributes => {:name => 'issue[description]'}
780 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
781 assert_no_tag 'select', :attributes => {:name => 'issue[priority_id]'}
782 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
783 assert_no_tag 'select', :attributes => {:name => 'issue[category_id]'}
784 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
785 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
786 assert_no_tag 'input', :attributes => {:name => 'issue[start_date]'}
787 assert_no_tag 'input', :attributes => {:name => 'issue[due_date]'}
788 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
789 assert_no_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]' }
790 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
791 assert_tag 'textarea', :attributes => {:name => 'notes'}
792 end
793
794 def test_show_should_not_display_update_form_without_permissions
795 Role.find(1).update_attribute :permissions, [:view_issues]
796
797 @request.session[:user_id] = 2
798 get :show, :id => 1
799 assert_response :success
800
801 assert_no_tag 'form', :attributes => {:id => 'issue-form'}
802 end
803
717 804 def test_update_form_should_not_display_inactive_enumerations
718 805 @request.session[:user_id] = 2
719 806 get :show, :id => 1
720 807 assert_response :success
721 808
722 809 assert ! IssuePriority.find(15).active?
723 810 assert_no_tag :option, :attributes => {:value => '15'},
724 811 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
725 812 end
726 813
727 814 def test_update_form_should_allow_attachment_upload
728 815 @request.session[:user_id] = 2
729 816 get :show, :id => 1
730 817
731 818 assert_tag :tag => 'form',
732 819 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
733 820 :descendant => {
734 821 :tag => 'input',
735 822 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
736 823 }
737 824 end
738 825
739 826 def test_show_should_deny_anonymous_access_without_permission
740 827 Role.anonymous.remove_permission!(:view_issues)
741 828 get :show, :id => 1
742 829 assert_response :redirect
743 830 end
744 831
745 832 def test_show_should_deny_anonymous_access_to_private_issue
746 833 Issue.update_all(["is_private = ?", true], "id = 1")
747 834 get :show, :id => 1
748 835 assert_response :redirect
749 836 end
750 837
751 838 def test_show_should_deny_non_member_access_without_permission
752 839 Role.non_member.remove_permission!(:view_issues)
753 840 @request.session[:user_id] = 9
754 841 get :show, :id => 1
755 842 assert_response 403
756 843 end
757 844
758 845 def test_show_should_deny_non_member_access_to_private_issue
759 846 Issue.update_all(["is_private = ?", true], "id = 1")
760 847 @request.session[:user_id] = 9
761 848 get :show, :id => 1
762 849 assert_response 403
763 850 end
764 851
765 852 def test_show_should_deny_member_access_without_permission
766 853 Role.find(1).remove_permission!(:view_issues)
767 854 @request.session[:user_id] = 2
768 855 get :show, :id => 1
769 856 assert_response 403
770 857 end
771 858
772 859 def test_show_should_deny_member_access_to_private_issue_without_permission
773 860 Issue.update_all(["is_private = ?", true], "id = 1")
774 861 @request.session[:user_id] = 3
775 862 get :show, :id => 1
776 863 assert_response 403
777 864 end
778 865
779 866 def test_show_should_allow_author_access_to_private_issue
780 867 Issue.update_all(["is_private = ?, author_id = 3", true], "id = 1")
781 868 @request.session[:user_id] = 3
782 869 get :show, :id => 1
783 870 assert_response :success
784 871 end
785 872
786 873 def test_show_should_allow_assignee_access_to_private_issue
787 874 Issue.update_all(["is_private = ?, assigned_to_id = 3", true], "id = 1")
788 875 @request.session[:user_id] = 3
789 876 get :show, :id => 1
790 877 assert_response :success
791 878 end
792 879
793 880 def test_show_should_allow_member_access_to_private_issue_with_permission
794 881 Issue.update_all(["is_private = ?", true], "id = 1")
795 882 User.find(3).roles_for_project(Project.find(1)).first.update_attribute :issues_visibility, 'all'
796 883 @request.session[:user_id] = 3
797 884 get :show, :id => 1
798 885 assert_response :success
799 886 end
800 887
801 888 def test_show_should_not_disclose_relations_to_invisible_issues
802 889 Setting.cross_project_issue_relations = '1'
803 890 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(2), :relation_type => 'relates')
804 891 # Relation to a private project issue
805 892 IssueRelation.create!(:issue_from => Issue.find(1), :issue_to => Issue.find(4), :relation_type => 'relates')
806 893
807 894 get :show, :id => 1
808 895 assert_response :success
809 896
810 897 assert_tag :div, :attributes => { :id => 'relations' },
811 898 :descendant => { :tag => 'a', :content => /#2$/ }
812 899 assert_no_tag :div, :attributes => { :id => 'relations' },
813 900 :descendant => { :tag => 'a', :content => /#4$/ }
814 901 end
815 902
816 903 def test_show_should_list_subtasks
817 904 Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
818 905
819 906 get :show, :id => 1
820 907 assert_response :success
821 908 assert_tag 'div', :attributes => {:id => 'issue_tree'},
822 909 :descendant => {:tag => 'td', :content => /Child Issue/, :attributes => {:class => /subject/}}
823 910 end
824 911
825 912 def test_show_should_list_parents
826 913 issue = Issue.generate!(:project_id => 1, :author_id => 1, :tracker_id => 1, :parent_issue_id => 1, :subject => 'Child Issue')
827 914
828 915 get :show, :id => issue.id
829 916 assert_response :success
830 917 assert_tag 'div', :attributes => {:class => 'subject'},
831 918 :descendant => {:tag => 'h3', :content => 'Child Issue'}
832 919 assert_tag 'div', :attributes => {:class => 'subject'},
833 920 :descendant => {:tag => 'a', :attributes => {:href => '/issues/1'}}
834 921 end
835 922
836 923 def test_show_atom
837 924 get :show, :id => 2, :format => 'atom'
838 925 assert_response :success
839 926 assert_template 'journals/index'
840 927 # Inline image
841 928 assert_select 'content', :text => Regexp.new(Regexp.quote('http://test.host/attachments/download/10'))
842 929 end
843 930
844 931 def test_show_export_to_pdf
845 932 get :show, :id => 3, :format => 'pdf'
846 933 assert_response :success
847 934 assert_equal 'application/pdf', @response.content_type
848 935 assert @response.body.starts_with?('%PDF')
849 936 assert_not_nil assigns(:issue)
850 937 end
851 938
852 939 def test_get_new
853 940 @request.session[:user_id] = 2
854 941 get :new, :project_id => 1, :tracker_id => 1
855 942 assert_response :success
856 943 assert_template 'new'
857 944
858 assert_tag :tag => 'input', :attributes => { :name => 'issue[custom_field_values][2]',
859 :value => 'Default string' }
945 assert_tag 'input', :attributes => {:name => 'issue[is_private]'}
946 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
947 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
948 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
949 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
950 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
951 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
952 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
953 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
954 assert_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
955 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
956 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
957 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
958 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
959 assert_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
860 960
861 961 # Be sure we don't display inactive IssuePriorities
862 962 assert ! IssuePriority.find(15).active?
863 963 assert_no_tag :option, :attributes => {:value => '15'},
864 964 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
865 965 end
866 966
967 def test_get_new_with_minimal_permissions
968 Role.find(1).update_attribute :permissions, [:add_issues]
969 Workflow.delete_all :role_id => 1
970
971 @request.session[:user_id] = 2
972 get :new, :project_id => 1, :tracker_id => 1
973 assert_response :success
974 assert_template 'new'
975
976 assert_no_tag 'input', :attributes => {:name => 'issue[is_private]'}
977 assert_tag 'select', :attributes => {:name => 'issue[tracker_id]'}
978 assert_tag 'input', :attributes => {:name => 'issue[subject]'}
979 assert_tag 'textarea', :attributes => {:name => 'issue[description]'}
980 assert_tag 'select', :attributes => {:name => 'issue[status_id]'}
981 assert_tag 'select', :attributes => {:name => 'issue[priority_id]'}
982 assert_tag 'select', :attributes => {:name => 'issue[assigned_to_id]'}
983 assert_tag 'select', :attributes => {:name => 'issue[category_id]'}
984 assert_tag 'select', :attributes => {:name => 'issue[fixed_version_id]'}
985 assert_no_tag 'input', :attributes => {:name => 'issue[parent_issue_id]'}
986 assert_tag 'input', :attributes => {:name => 'issue[start_date]'}
987 assert_tag 'input', :attributes => {:name => 'issue[due_date]'}
988 assert_tag 'select', :attributes => {:name => 'issue[done_ratio]'}
989 assert_tag 'input', :attributes => { :name => 'issue[custom_field_values][2]', :value => 'Default string' }
990 assert_no_tag 'input', :attributes => {:name => 'issue[watcher_user_ids][]'}
991 end
992
867 993 def test_get_new_without_default_start_date_is_creation_date
868 994 Setting.default_issue_start_date_to_creation_date = 0
869 995
870 996 @request.session[:user_id] = 2
871 997 get :new, :project_id => 1, :tracker_id => 1
872 998 assert_response :success
873 999 assert_template 'new'
874 1000
875 1001 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
876 1002 :value => nil }
877 1003 end
878 1004
879 1005 def test_get_new_with_default_start_date_is_creation_date
880 1006 Setting.default_issue_start_date_to_creation_date = 1
881 1007
882 1008 @request.session[:user_id] = 2
883 1009 get :new, :project_id => 1, :tracker_id => 1
884 1010 assert_response :success
885 1011 assert_template 'new'
886 1012
887 1013 assert_tag :tag => 'input', :attributes => { :name => 'issue[start_date]',
888 1014 :value => Date.today.to_s }
889 1015 end
890 1016
891 1017 def test_get_new_form_should_allow_attachment_upload
892 1018 @request.session[:user_id] = 2
893 1019 get :new, :project_id => 1, :tracker_id => 1
894 1020
895 1021 assert_tag :tag => 'form',
896 1022 :attributes => {:id => 'issue-form', :method => 'post', :enctype => 'multipart/form-data'},
897 1023 :descendant => {
898 1024 :tag => 'input',
899 1025 :attributes => {:type => 'file', :name => 'attachments[1][file]'}
900 1026 }
901 1027 end
902 1028
903 1029 def test_get_new_without_tracker_id
904 1030 @request.session[:user_id] = 2
905 1031 get :new, :project_id => 1
906 1032 assert_response :success
907 1033 assert_template 'new'
908 1034
909 1035 issue = assigns(:issue)
910 1036 assert_not_nil issue
911 1037 assert_equal Project.find(1).trackers.first, issue.tracker
912 1038 end
913 1039
914 1040 def test_get_new_with_no_default_status_should_display_an_error
915 1041 @request.session[:user_id] = 2
916 1042 IssueStatus.delete_all
917 1043
918 1044 get :new, :project_id => 1
919 1045 assert_response 500
920 1046 assert_error_tag :content => /No default issue/
921 1047 end
922 1048
923 1049 def test_get_new_with_no_tracker_should_display_an_error
924 1050 @request.session[:user_id] = 2
925 1051 Tracker.delete_all
926 1052
927 1053 get :new, :project_id => 1
928 1054 assert_response 500
929 1055 assert_error_tag :content => /No tracker/
930 1056 end
931 1057
932 1058 def test_update_new_form
933 1059 @request.session[:user_id] = 2
934 1060 xhr :post, :new, :project_id => 1,
935 1061 :issue => {:tracker_id => 2,
936 1062 :subject => 'This is the test_new issue',
937 1063 :description => 'This is the description',
938 1064 :priority_id => 5}
939 1065 assert_response :success
940 1066 assert_template 'attributes'
941 1067
942 1068 issue = assigns(:issue)
943 1069 assert_kind_of Issue, issue
944 1070 assert_equal 1, issue.project_id
945 1071 assert_equal 2, issue.tracker_id
946 1072 assert_equal 'This is the test_new issue', issue.subject
947 1073 end
948 1074
949 1075 def test_post_create
950 1076 @request.session[:user_id] = 2
951 1077 assert_difference 'Issue.count' do
952 1078 post :create, :project_id => 1,
953 1079 :issue => {:tracker_id => 3,
954 1080 :status_id => 2,
955 1081 :subject => 'This is the test_new issue',
956 1082 :description => 'This is the description',
957 1083 :priority_id => 5,
958 1084 :start_date => '2010-11-07',
959 1085 :estimated_hours => '',
960 1086 :custom_field_values => {'2' => 'Value for field 2'}}
961 1087 end
962 1088 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
963 1089
964 1090 issue = Issue.find_by_subject('This is the test_new issue')
965 1091 assert_not_nil issue
966 1092 assert_equal 2, issue.author_id
967 1093 assert_equal 3, issue.tracker_id
968 1094 assert_equal 2, issue.status_id
969 1095 assert_equal Date.parse('2010-11-07'), issue.start_date
970 1096 assert_nil issue.estimated_hours
971 1097 v = issue.custom_values.find(:first, :conditions => {:custom_field_id => 2})
972 1098 assert_not_nil v
973 1099 assert_equal 'Value for field 2', v.value
974 1100 end
975 1101
976 1102 def test_post_new_with_group_assignment
977 1103 group = Group.find(11)
978 1104 project = Project.find(1)
979 1105 project.members << Member.new(:principal => group, :roles => [Role.first])
980 1106
981 1107 with_settings :issue_group_assignment => '1' do
982 1108 @request.session[:user_id] = 2
983 1109 assert_difference 'Issue.count' do
984 1110 post :create, :project_id => project.id,
985 1111 :issue => {:tracker_id => 3,
986 1112 :status_id => 1,
987 1113 :subject => 'This is the test_new_with_group_assignment issue',
988 1114 :assigned_to_id => group.id}
989 1115 end
990 1116 end
991 1117 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
992 1118
993 1119 issue = Issue.find_by_subject('This is the test_new_with_group_assignment issue')
994 1120 assert_not_nil issue
995 1121 assert_equal group, issue.assigned_to
996 1122 end
997 1123
998 1124 def test_post_create_without_start_date_and_default_start_date_is_not_creation_date
999 1125 Setting.default_issue_start_date_to_creation_date = 0
1000 1126
1001 1127 @request.session[:user_id] = 2
1002 1128 assert_difference 'Issue.count' do
1003 1129 post :create, :project_id => 1,
1004 1130 :issue => {:tracker_id => 3,
1005 1131 :status_id => 2,
1006 1132 :subject => 'This is the test_new issue',
1007 1133 :description => 'This is the description',
1008 1134 :priority_id => 5,
1009 1135 :estimated_hours => '',
1010 1136 :custom_field_values => {'2' => 'Value for field 2'}}
1011 1137 end
1012 1138 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1013 1139
1014 1140 issue = Issue.find_by_subject('This is the test_new issue')
1015 1141 assert_not_nil issue
1016 1142 assert_nil issue.start_date
1017 1143 end
1018 1144
1019 1145 def test_post_create_without_start_date_and_default_start_date_is_creation_date
1020 1146 Setting.default_issue_start_date_to_creation_date = 1
1021 1147
1022 1148 @request.session[:user_id] = 2
1023 1149 assert_difference 'Issue.count' do
1024 1150 post :create, :project_id => 1,
1025 1151 :issue => {:tracker_id => 3,
1026 1152 :status_id => 2,
1027 1153 :subject => 'This is the test_new issue',
1028 1154 :description => 'This is the description',
1029 1155 :priority_id => 5,
1030 1156 :estimated_hours => '',
1031 1157 :custom_field_values => {'2' => 'Value for field 2'}}
1032 1158 end
1033 1159 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1034 1160
1035 1161 issue = Issue.find_by_subject('This is the test_new issue')
1036 1162 assert_not_nil issue
1037 1163 assert_equal Date.today, issue.start_date
1038 1164 end
1039 1165
1040 1166 def test_post_create_and_continue
1041 1167 @request.session[:user_id] = 2
1042 1168 assert_difference 'Issue.count' do
1043 1169 post :create, :project_id => 1,
1044 1170 :issue => {:tracker_id => 3, :subject => 'This is first issue', :priority_id => 5},
1045 1171 :continue => ''
1046 1172 end
1047 1173
1048 1174 issue = Issue.first(:order => 'id DESC')
1049 1175 assert_redirected_to :controller => 'issues', :action => 'new', :project_id => 'ecookbook', :issue => {:tracker_id => 3}
1050 1176 assert_not_nil flash[:notice], "flash was not set"
1051 1177 assert flash[:notice].include?("<a href='/issues/#{issue.id}'>##{issue.id}</a>"), "issue link not found in flash: #{flash[:notice]}"
1052 1178 end
1053 1179
1054 1180 def test_post_create_without_custom_fields_param
1055 1181 @request.session[:user_id] = 2
1056 1182 assert_difference 'Issue.count' do
1057 1183 post :create, :project_id => 1,
1058 1184 :issue => {:tracker_id => 1,
1059 1185 :subject => 'This is the test_new issue',
1060 1186 :description => 'This is the description',
1061 1187 :priority_id => 5}
1062 1188 end
1063 1189 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1064 1190 end
1065 1191
1066 1192 def test_post_create_with_required_custom_field_and_without_custom_fields_param
1067 1193 field = IssueCustomField.find_by_name('Database')
1068 1194 field.update_attribute(:is_required, true)
1069 1195
1070 1196 @request.session[:user_id] = 2
1071 1197 post :create, :project_id => 1,
1072 1198 :issue => {:tracker_id => 1,
1073 1199 :subject => 'This is the test_new issue',
1074 1200 :description => 'This is the description',
1075 1201 :priority_id => 5}
1076 1202 assert_response :success
1077 1203 assert_template 'new'
1078 1204 issue = assigns(:issue)
1079 1205 assert_not_nil issue
1080 1206 assert_equal I18n.translate('activerecord.errors.messages.invalid'),
1081 1207 issue.errors[:custom_values].to_s
1082 1208 end
1083 1209
1084 1210 def test_post_create_with_watchers
1085 1211 @request.session[:user_id] = 2
1086 1212 ActionMailer::Base.deliveries.clear
1087 1213
1088 1214 assert_difference 'Watcher.count', 2 do
1089 1215 post :create, :project_id => 1,
1090 1216 :issue => {:tracker_id => 1,
1091 1217 :subject => 'This is a new issue with watchers',
1092 1218 :description => 'This is the description',
1093 1219 :priority_id => 5,
1094 1220 :watcher_user_ids => ['2', '3']}
1095 1221 end
1096 1222 issue = Issue.find_by_subject('This is a new issue with watchers')
1097 1223 assert_not_nil issue
1098 1224 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue
1099 1225
1100 1226 # Watchers added
1101 1227 assert_equal [2, 3], issue.watcher_user_ids.sort
1102 1228 assert issue.watched_by?(User.find(3))
1103 1229 # Watchers notified
1104 1230 mail = ActionMailer::Base.deliveries.last
1105 1231 assert_kind_of TMail::Mail, mail
1106 1232 assert [mail.bcc, mail.cc].flatten.include?(User.find(3).mail)
1107 1233 end
1108 1234
1109 1235 def test_post_create_subissue
1110 1236 @request.session[:user_id] = 2
1111 1237
1112 1238 assert_difference 'Issue.count' do
1113 1239 post :create, :project_id => 1,
1114 1240 :issue => {:tracker_id => 1,
1115 1241 :subject => 'This is a child issue',
1116 1242 :parent_issue_id => 2}
1117 1243 end
1118 1244 issue = Issue.find_by_subject('This is a child issue')
1119 1245 assert_not_nil issue
1120 1246 assert_equal Issue.find(2), issue.parent
1121 1247 end
1122 1248
1123 1249 def test_post_create_subissue_with_non_numeric_parent_id
1124 1250 @request.session[:user_id] = 2
1125 1251
1126 1252 assert_difference 'Issue.count' do
1127 1253 post :create, :project_id => 1,
1128 1254 :issue => {:tracker_id => 1,
1129 1255 :subject => 'This is a child issue',
1130 1256 :parent_issue_id => 'ABC'}
1131 1257 end
1132 1258 issue = Issue.find_by_subject('This is a child issue')
1133 1259 assert_not_nil issue
1134 1260 assert_nil issue.parent
1135 1261 end
1136 1262
1137 1263 def test_post_create_private
1138 1264 @request.session[:user_id] = 2
1139 1265
1140 1266 assert_difference 'Issue.count' do
1141 1267 post :create, :project_id => 1,
1142 1268 :issue => {:tracker_id => 1,
1143 1269 :subject => 'This is a private issue',
1144 1270 :is_private => '1'}
1145 1271 end
1146 1272 issue = Issue.first(:order => 'id DESC')
1147 1273 assert issue.is_private?
1148 1274 end
1149 1275
1150 1276 def test_post_create_private_with_set_own_issues_private_permission
1151 1277 role = Role.find(1)
1152 1278 role.remove_permission! :set_issues_private
1153 1279 role.add_permission! :set_own_issues_private
1154 1280
1155 1281 @request.session[:user_id] = 2
1156 1282
1157 1283 assert_difference 'Issue.count' do
1158 1284 post :create, :project_id => 1,
1159 1285 :issue => {:tracker_id => 1,
1160 1286 :subject => 'This is a private issue',
1161 1287 :is_private => '1'}
1162 1288 end
1163 1289 issue = Issue.first(:order => 'id DESC')
1164 1290 assert issue.is_private?
1165 1291 end
1166 1292
1167 1293 def test_post_create_should_send_a_notification
1168 1294 ActionMailer::Base.deliveries.clear
1169 1295 @request.session[:user_id] = 2
1170 1296 assert_difference 'Issue.count' do
1171 1297 post :create, :project_id => 1,
1172 1298 :issue => {:tracker_id => 3,
1173 1299 :subject => 'This is the test_new issue',
1174 1300 :description => 'This is the description',
1175 1301 :priority_id => 5,
1176 1302 :estimated_hours => '',
1177 1303 :custom_field_values => {'2' => 'Value for field 2'}}
1178 1304 end
1179 1305 assert_redirected_to :controller => 'issues', :action => 'show', :id => Issue.last.id
1180 1306
1181 1307 assert_equal 1, ActionMailer::Base.deliveries.size
1182 1308 end
1183 1309
1184 1310 def test_post_create_should_preserve_fields_values_on_validation_failure
1185 1311 @request.session[:user_id] = 2
1186 1312 post :create, :project_id => 1,
1187 1313 :issue => {:tracker_id => 1,
1188 1314 # empty subject
1189 1315 :subject => '',
1190 1316 :description => 'This is a description',
1191 1317 :priority_id => 6,
1192 1318 :custom_field_values => {'1' => 'Oracle', '2' => 'Value for field 2'}}
1193 1319 assert_response :success
1194 1320 assert_template 'new'
1195 1321
1196 1322 assert_tag :textarea, :attributes => { :name => 'issue[description]' },
1197 1323 :content => 'This is a description'
1198 1324 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1199 1325 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1200 1326 :value => '6' },
1201 1327 :content => 'High' }
1202 1328 # Custom fields
1203 1329 assert_tag :select, :attributes => { :name => 'issue[custom_field_values][1]' },
1204 1330 :child => { :tag => 'option', :attributes => { :selected => 'selected',
1205 1331 :value => 'Oracle' },
1206 1332 :content => 'Oracle' }
1207 1333 assert_tag :input, :attributes => { :name => 'issue[custom_field_values][2]',
1208 1334 :value => 'Value for field 2'}
1209 1335 end
1210 1336
1211 1337 def test_post_create_should_ignore_non_safe_attributes
1212 1338 @request.session[:user_id] = 2
1213 1339 assert_nothing_raised do
1214 1340 post :create, :project_id => 1, :issue => { :tracker => "A param can not be a Tracker" }
1215 1341 end
1216 1342 end
1217 1343
1218 1344 def test_post_create_with_attachment
1219 1345 set_tmp_attachments_directory
1220 1346 @request.session[:user_id] = 2
1221 1347
1222 1348 assert_difference 'Issue.count' do
1223 1349 assert_difference 'Attachment.count' do
1224 1350 post :create, :project_id => 1,
1225 1351 :issue => { :tracker_id => '1', :subject => 'With attachment' },
1226 1352 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1227 1353 end
1228 1354 end
1229 1355
1230 1356 issue = Issue.first(:order => 'id DESC')
1231 1357 attachment = Attachment.first(:order => 'id DESC')
1232 1358
1233 1359 assert_equal issue, attachment.container
1234 1360 assert_equal 2, attachment.author_id
1235 1361 assert_equal 'testfile.txt', attachment.filename
1236 1362 assert_equal 'text/plain', attachment.content_type
1237 1363 assert_equal 'test file', attachment.description
1238 1364 assert_equal 59, attachment.filesize
1239 1365 assert File.exists?(attachment.diskfile)
1240 1366 assert_equal 59, File.size(attachment.diskfile)
1241 1367 end
1242 1368
1243 1369 context "without workflow privilege" do
1244 1370 setup do
1245 1371 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1246 1372 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1247 1373 end
1248 1374
1249 1375 context "#new" do
1250 1376 should "propose default status only" do
1251 1377 get :new, :project_id => 1
1252 1378 assert_response :success
1253 1379 assert_template 'new'
1254 1380 assert_tag :tag => 'select',
1255 1381 :attributes => {:name => 'issue[status_id]'},
1256 1382 :children => {:count => 1},
1257 1383 :child => {:tag => 'option', :attributes => {:value => IssueStatus.default.id.to_s}}
1258 1384 end
1259 1385
1260 1386 should "accept default status" do
1261 1387 assert_difference 'Issue.count' do
1262 1388 post :create, :project_id => 1,
1263 1389 :issue => {:tracker_id => 1,
1264 1390 :subject => 'This is an issue',
1265 1391 :status_id => 1}
1266 1392 end
1267 1393 issue = Issue.last(:order => 'id')
1268 1394 assert_equal IssueStatus.default, issue.status
1269 1395 end
1270 1396
1271 1397 should "ignore unauthorized status" do
1272 1398 assert_difference 'Issue.count' do
1273 1399 post :create, :project_id => 1,
1274 1400 :issue => {:tracker_id => 1,
1275 1401 :subject => 'This is an issue',
1276 1402 :status_id => 3}
1277 1403 end
1278 1404 issue = Issue.last(:order => 'id')
1279 1405 assert_equal IssueStatus.default, issue.status
1280 1406 end
1281 1407 end
1282 1408
1283 1409 context "#update" do
1284 1410 should "ignore status change" do
1285 1411 assert_difference 'Journal.count' do
1286 1412 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1287 1413 end
1288 1414 assert_equal 1, Issue.find(1).status_id
1289 1415 end
1290 1416
1291 1417 should "ignore attributes changes" do
1292 1418 assert_difference 'Journal.count' do
1293 1419 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1294 1420 end
1295 1421 issue = Issue.find(1)
1296 1422 assert_equal "Can't print recipes", issue.subject
1297 1423 assert_nil issue.assigned_to
1298 1424 end
1299 1425 end
1300 1426 end
1301 1427
1302 1428 context "with workflow privilege" do
1303 1429 setup do
1304 1430 Workflow.delete_all(["role_id = ?", Role.anonymous.id])
1305 1431 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 3)
1306 1432 Workflow.create!(:role => Role.anonymous, :tracker_id => 1, :old_status_id => 1, :new_status_id => 4)
1307 1433 Role.anonymous.add_permission! :add_issues, :add_issue_notes
1308 1434 end
1309 1435
1310 1436 context "#update" do
1311 1437 should "accept authorized status" do
1312 1438 assert_difference 'Journal.count' do
1313 1439 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1314 1440 end
1315 1441 assert_equal 3, Issue.find(1).status_id
1316 1442 end
1317 1443
1318 1444 should "ignore unauthorized status" do
1319 1445 assert_difference 'Journal.count' do
1320 1446 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1321 1447 end
1322 1448 assert_equal 1, Issue.find(1).status_id
1323 1449 end
1324 1450
1325 1451 should "accept authorized attributes changes" do
1326 1452 assert_difference 'Journal.count' do
1327 1453 put :update, :id => 1, :notes => 'just trying', :issue => {:assigned_to_id => 2}
1328 1454 end
1329 1455 issue = Issue.find(1)
1330 1456 assert_equal 2, issue.assigned_to_id
1331 1457 end
1332 1458
1333 1459 should "ignore unauthorized attributes changes" do
1334 1460 assert_difference 'Journal.count' do
1335 1461 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed'}
1336 1462 end
1337 1463 issue = Issue.find(1)
1338 1464 assert_equal "Can't print recipes", issue.subject
1339 1465 end
1340 1466 end
1341 1467
1342 1468 context "and :edit_issues permission" do
1343 1469 setup do
1344 1470 Role.anonymous.add_permission! :add_issues, :edit_issues
1345 1471 end
1346 1472
1347 1473 should "accept authorized status" do
1348 1474 assert_difference 'Journal.count' do
1349 1475 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 3}
1350 1476 end
1351 1477 assert_equal 3, Issue.find(1).status_id
1352 1478 end
1353 1479
1354 1480 should "ignore unauthorized status" do
1355 1481 assert_difference 'Journal.count' do
1356 1482 put :update, :id => 1, :notes => 'just trying', :issue => {:status_id => 2}
1357 1483 end
1358 1484 assert_equal 1, Issue.find(1).status_id
1359 1485 end
1360 1486
1361 1487 should "accept authorized attributes changes" do
1362 1488 assert_difference 'Journal.count' do
1363 1489 put :update, :id => 1, :notes => 'just trying', :issue => {:subject => 'changed', :assigned_to_id => 2}
1364 1490 end
1365 1491 issue = Issue.find(1)
1366 1492 assert_equal "changed", issue.subject
1367 1493 assert_equal 2, issue.assigned_to_id
1368 1494 end
1369 1495 end
1370 1496 end
1371 1497
1372 1498 def test_copy_issue
1373 1499 @request.session[:user_id] = 2
1374 1500 get :new, :project_id => 1, :copy_from => 1
1375 1501 assert_template 'new'
1376 1502 assert_not_nil assigns(:issue)
1377 1503 orig = Issue.find(1)
1378 1504 assert_equal orig.subject, assigns(:issue).subject
1379 1505 end
1380 1506
1381 1507 def test_get_edit
1382 1508 @request.session[:user_id] = 2
1383 1509 get :edit, :id => 1
1384 1510 assert_response :success
1385 1511 assert_template 'edit'
1386 1512 assert_not_nil assigns(:issue)
1387 1513 assert_equal Issue.find(1), assigns(:issue)
1388 1514
1389 1515 # Be sure we don't display inactive IssuePriorities
1390 1516 assert ! IssuePriority.find(15).active?
1391 1517 assert_no_tag :option, :attributes => {:value => '15'},
1392 1518 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1393 1519 end
1394 1520
1395 1521 def test_get_edit_should_display_the_time_entry_form_with_log_time_permission
1396 1522 @request.session[:user_id] = 2
1397 1523 Role.find_by_name('Manager').update_attribute :permissions, [:view_issues, :edit_issues, :log_time]
1398 1524
1399 1525 get :edit, :id => 1
1400 1526 assert_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1401 1527 end
1402 1528
1403 1529 def test_get_edit_should_not_display_the_time_entry_form_without_log_time_permission
1404 1530 @request.session[:user_id] = 2
1405 1531 Role.find_by_name('Manager').remove_permission! :log_time
1406 1532
1407 1533 get :edit, :id => 1
1408 1534 assert_no_tag 'input', :attributes => {:name => 'time_entry[hours]'}
1409 1535 end
1410 1536
1411 1537 def test_get_edit_with_params
1412 1538 @request.session[:user_id] = 2
1413 1539 get :edit, :id => 1, :issue => { :status_id => 5, :priority_id => 7 },
1414 1540 :time_entry => { :hours => '2.5', :comments => 'test_get_edit_with_params', :activity_id => TimeEntryActivity.first.id }
1415 1541 assert_response :success
1416 1542 assert_template 'edit'
1417 1543
1418 1544 issue = assigns(:issue)
1419 1545 assert_not_nil issue
1420 1546
1421 1547 assert_equal 5, issue.status_id
1422 1548 assert_tag :select, :attributes => { :name => 'issue[status_id]' },
1423 1549 :child => { :tag => 'option',
1424 1550 :content => 'Closed',
1425 1551 :attributes => { :selected => 'selected' } }
1426 1552
1427 1553 assert_equal 7, issue.priority_id
1428 1554 assert_tag :select, :attributes => { :name => 'issue[priority_id]' },
1429 1555 :child => { :tag => 'option',
1430 1556 :content => 'Urgent',
1431 1557 :attributes => { :selected => 'selected' } }
1432 1558
1433 1559 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => '2.5' }
1434 1560 assert_tag :select, :attributes => { :name => 'time_entry[activity_id]' },
1435 1561 :child => { :tag => 'option',
1436 1562 :attributes => { :selected => 'selected', :value => TimeEntryActivity.first.id } }
1437 1563 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => 'test_get_edit_with_params' }
1438 1564 end
1439 1565
1440 1566 def test_update_edit_form
1441 1567 @request.session[:user_id] = 2
1442 1568 xhr :post, :new, :project_id => 1,
1443 1569 :id => 1,
1444 1570 :issue => {:tracker_id => 2,
1445 1571 :subject => 'This is the test_new issue',
1446 1572 :description => 'This is the description',
1447 1573 :priority_id => 5}
1448 1574 assert_response :success
1449 1575 assert_template 'attributes'
1450 1576
1451 1577 issue = assigns(:issue)
1452 1578 assert_kind_of Issue, issue
1453 1579 assert_equal 1, issue.id
1454 1580 assert_equal 1, issue.project_id
1455 1581 assert_equal 2, issue.tracker_id
1456 1582 assert_equal 'This is the test_new issue', issue.subject
1457 1583 end
1458 1584
1459 1585 def test_update_using_invalid_http_verbs
1460 1586 @request.session[:user_id] = 2
1461 1587 subject = 'Updated by an invalid http verb'
1462 1588
1463 1589 get :update, :id => 1, :issue => {:subject => subject}
1464 1590 assert_not_equal subject, Issue.find(1).subject
1465 1591
1466 1592 post :update, :id => 1, :issue => {:subject => subject}
1467 1593 assert_not_equal subject, Issue.find(1).subject
1468 1594
1469 1595 delete :update, :id => 1, :issue => {:subject => subject}
1470 1596 assert_not_equal subject, Issue.find(1).subject
1471 1597 end
1472 1598
1473 1599 def test_put_update_without_custom_fields_param
1474 1600 @request.session[:user_id] = 2
1475 1601 ActionMailer::Base.deliveries.clear
1476 1602
1477 1603 issue = Issue.find(1)
1478 1604 assert_equal '125', issue.custom_value_for(2).value
1479 1605 old_subject = issue.subject
1480 1606 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1481 1607
1482 1608 assert_difference('Journal.count') do
1483 1609 assert_difference('JournalDetail.count', 2) do
1484 1610 put :update, :id => 1, :issue => {:subject => new_subject,
1485 1611 :priority_id => '6',
1486 1612 :category_id => '1' # no change
1487 1613 }
1488 1614 end
1489 1615 end
1490 1616 assert_redirected_to :action => 'show', :id => '1'
1491 1617 issue.reload
1492 1618 assert_equal new_subject, issue.subject
1493 1619 # Make sure custom fields were not cleared
1494 1620 assert_equal '125', issue.custom_value_for(2).value
1495 1621
1496 1622 mail = ActionMailer::Base.deliveries.last
1497 1623 assert_kind_of TMail::Mail, mail
1498 1624 assert mail.subject.starts_with?("[#{issue.project.name} - #{issue.tracker.name} ##{issue.id}]")
1499 1625 assert mail.body.include?("Subject changed from #{old_subject} to #{new_subject}")
1500 1626 end
1501 1627
1502 1628 def test_put_update_with_custom_field_change
1503 1629 @request.session[:user_id] = 2
1504 1630 issue = Issue.find(1)
1505 1631 assert_equal '125', issue.custom_value_for(2).value
1506 1632
1507 1633 assert_difference('Journal.count') do
1508 1634 assert_difference('JournalDetail.count', 3) do
1509 1635 put :update, :id => 1, :issue => {:subject => 'Custom field change',
1510 1636 :priority_id => '6',
1511 1637 :category_id => '1', # no change
1512 1638 :custom_field_values => { '2' => 'New custom value' }
1513 1639 }
1514 1640 end
1515 1641 end
1516 1642 assert_redirected_to :action => 'show', :id => '1'
1517 1643 issue.reload
1518 1644 assert_equal 'New custom value', issue.custom_value_for(2).value
1519 1645
1520 1646 mail = ActionMailer::Base.deliveries.last
1521 1647 assert_kind_of TMail::Mail, mail
1522 1648 assert mail.body.include?("Searchable field changed from 125 to New custom value")
1523 1649 end
1524 1650
1525 1651 def test_put_update_with_status_and_assignee_change
1526 1652 issue = Issue.find(1)
1527 1653 assert_equal 1, issue.status_id
1528 1654 @request.session[:user_id] = 2
1529 1655 assert_difference('TimeEntry.count', 0) do
1530 1656 put :update,
1531 1657 :id => 1,
1532 1658 :issue => { :status_id => 2, :assigned_to_id => 3 },
1533 1659 :notes => 'Assigned to dlopper',
1534 1660 :time_entry => { :hours => '', :comments => '', :activity_id => TimeEntryActivity.first }
1535 1661 end
1536 1662 assert_redirected_to :action => 'show', :id => '1'
1537 1663 issue.reload
1538 1664 assert_equal 2, issue.status_id
1539 1665 j = Journal.find(:first, :order => 'id DESC')
1540 1666 assert_equal 'Assigned to dlopper', j.notes
1541 1667 assert_equal 2, j.details.size
1542 1668
1543 1669 mail = ActionMailer::Base.deliveries.last
1544 1670 assert mail.body.include?("Status changed from New to Assigned")
1545 1671 # subject should contain the new status
1546 1672 assert mail.subject.include?("(#{ IssueStatus.find(2).name })")
1547 1673 end
1548 1674
1549 1675 def test_put_update_with_note_only
1550 1676 notes = 'Note added by IssuesControllerTest#test_update_with_note_only'
1551 1677 # anonymous user
1552 1678 put :update,
1553 1679 :id => 1,
1554 1680 :notes => notes
1555 1681 assert_redirected_to :action => 'show', :id => '1'
1556 1682 j = Journal.find(:first, :order => 'id DESC')
1557 1683 assert_equal notes, j.notes
1558 1684 assert_equal 0, j.details.size
1559 1685 assert_equal User.anonymous, j.user
1560 1686
1561 1687 mail = ActionMailer::Base.deliveries.last
1562 1688 assert mail.body.include?(notes)
1563 1689 end
1564 1690
1565 1691 def test_put_update_with_note_and_spent_time
1566 1692 @request.session[:user_id] = 2
1567 1693 spent_hours_before = Issue.find(1).spent_hours
1568 1694 assert_difference('TimeEntry.count') do
1569 1695 put :update,
1570 1696 :id => 1,
1571 1697 :notes => '2.5 hours added',
1572 1698 :time_entry => { :hours => '2.5', :comments => 'test_put_update_with_note_and_spent_time', :activity_id => TimeEntryActivity.first.id }
1573 1699 end
1574 1700 assert_redirected_to :action => 'show', :id => '1'
1575 1701
1576 1702 issue = Issue.find(1)
1577 1703
1578 1704 j = Journal.find(:first, :order => 'id DESC')
1579 1705 assert_equal '2.5 hours added', j.notes
1580 1706 assert_equal 0, j.details.size
1581 1707
1582 1708 t = issue.time_entries.find_by_comments('test_put_update_with_note_and_spent_time')
1583 1709 assert_not_nil t
1584 1710 assert_equal 2.5, t.hours
1585 1711 assert_equal spent_hours_before + 2.5, issue.spent_hours
1586 1712 end
1587 1713
1588 1714 def test_put_update_with_attachment_only
1589 1715 set_tmp_attachments_directory
1590 1716
1591 1717 # Delete all fixtured journals, a race condition can occur causing the wrong
1592 1718 # journal to get fetched in the next find.
1593 1719 Journal.delete_all
1594 1720
1595 1721 # anonymous user
1596 1722 assert_difference 'Attachment.count' do
1597 1723 put :update, :id => 1,
1598 1724 :notes => '',
1599 1725 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain'), 'description' => 'test file'}}
1600 1726 end
1601 1727
1602 1728 assert_redirected_to :action => 'show', :id => '1'
1603 1729 j = Issue.find(1).journals.find(:first, :order => 'id DESC')
1604 1730 assert j.notes.blank?
1605 1731 assert_equal 1, j.details.size
1606 1732 assert_equal 'testfile.txt', j.details.first.value
1607 1733 assert_equal User.anonymous, j.user
1608 1734
1609 1735 attachment = Attachment.first(:order => 'id DESC')
1610 1736 assert_equal Issue.find(1), attachment.container
1611 1737 assert_equal User.anonymous, attachment.author
1612 1738 assert_equal 'testfile.txt', attachment.filename
1613 1739 assert_equal 'text/plain', attachment.content_type
1614 1740 assert_equal 'test file', attachment.description
1615 1741 assert_equal 59, attachment.filesize
1616 1742 assert File.exists?(attachment.diskfile)
1617 1743 assert_equal 59, File.size(attachment.diskfile)
1618 1744
1619 1745 mail = ActionMailer::Base.deliveries.last
1620 1746 assert mail.body.include?('testfile.txt')
1621 1747 end
1622 1748
1623 1749 def test_put_update_with_attachment_that_fails_to_save
1624 1750 set_tmp_attachments_directory
1625 1751
1626 1752 # Delete all fixtured journals, a race condition can occur causing the wrong
1627 1753 # journal to get fetched in the next find.
1628 1754 Journal.delete_all
1629 1755
1630 1756 # Mock out the unsaved attachment
1631 1757 Attachment.any_instance.stubs(:create).returns(Attachment.new)
1632 1758
1633 1759 # anonymous user
1634 1760 put :update,
1635 1761 :id => 1,
1636 1762 :notes => '',
1637 1763 :attachments => {'1' => {'file' => uploaded_test_file('testfile.txt', 'text/plain')}}
1638 1764 assert_redirected_to :action => 'show', :id => '1'
1639 1765 assert_equal '1 file(s) could not be saved.', flash[:warning]
1640 1766
1641 1767 end if Object.const_defined?(:Mocha)
1642 1768
1643 1769 def test_put_update_with_no_change
1644 1770 issue = Issue.find(1)
1645 1771 issue.journals.clear
1646 1772 ActionMailer::Base.deliveries.clear
1647 1773
1648 1774 put :update,
1649 1775 :id => 1,
1650 1776 :notes => ''
1651 1777 assert_redirected_to :action => 'show', :id => '1'
1652 1778
1653 1779 issue.reload
1654 1780 assert issue.journals.empty?
1655 1781 # No email should be sent
1656 1782 assert ActionMailer::Base.deliveries.empty?
1657 1783 end
1658 1784
1659 1785 def test_put_update_should_send_a_notification
1660 1786 @request.session[:user_id] = 2
1661 1787 ActionMailer::Base.deliveries.clear
1662 1788 issue = Issue.find(1)
1663 1789 old_subject = issue.subject
1664 1790 new_subject = 'Subject modified by IssuesControllerTest#test_post_edit'
1665 1791
1666 1792 put :update, :id => 1, :issue => {:subject => new_subject,
1667 1793 :priority_id => '6',
1668 1794 :category_id => '1' # no change
1669 1795 }
1670 1796 assert_equal 1, ActionMailer::Base.deliveries.size
1671 1797 end
1672 1798
1673 1799 def test_put_update_with_invalid_spent_time_hours_only
1674 1800 @request.session[:user_id] = 2
1675 1801 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1676 1802
1677 1803 assert_no_difference('Journal.count') do
1678 1804 put :update,
1679 1805 :id => 1,
1680 1806 :notes => notes,
1681 1807 :time_entry => {"comments"=>"", "activity_id"=>"", "hours"=>"2z"}
1682 1808 end
1683 1809 assert_response :success
1684 1810 assert_template 'edit'
1685 1811
1686 1812 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1687 1813 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1688 1814 assert_tag :input, :attributes => { :name => 'time_entry[hours]', :value => "2z" }
1689 1815 end
1690 1816
1691 1817 def test_put_update_with_invalid_spent_time_comments_only
1692 1818 @request.session[:user_id] = 2
1693 1819 notes = 'Note added by IssuesControllerTest#test_post_edit_with_invalid_spent_time'
1694 1820
1695 1821 assert_no_difference('Journal.count') do
1696 1822 put :update,
1697 1823 :id => 1,
1698 1824 :notes => notes,
1699 1825 :time_entry => {"comments"=>"this is my comment", "activity_id"=>"", "hours"=>""}
1700 1826 end
1701 1827 assert_response :success
1702 1828 assert_template 'edit'
1703 1829
1704 1830 assert_error_tag :descendant => {:content => /Activity can't be blank/}
1705 1831 assert_error_tag :descendant => {:content => /Hours can't be blank/}
1706 1832 assert_tag :textarea, :attributes => { :name => 'notes' }, :content => notes
1707 1833 assert_tag :input, :attributes => { :name => 'time_entry[comments]', :value => "this is my comment" }
1708 1834 end
1709 1835
1710 1836 def test_put_update_should_allow_fixed_version_to_be_set_to_a_subproject
1711 1837 issue = Issue.find(2)
1712 1838 @request.session[:user_id] = 2
1713 1839
1714 1840 put :update,
1715 1841 :id => issue.id,
1716 1842 :issue => {
1717 1843 :fixed_version_id => 4
1718 1844 }
1719 1845
1720 1846 assert_response :redirect
1721 1847 issue.reload
1722 1848 assert_equal 4, issue.fixed_version_id
1723 1849 assert_not_equal issue.project_id, issue.fixed_version.project_id
1724 1850 end
1725 1851
1726 1852 def test_put_update_should_redirect_back_using_the_back_url_parameter
1727 1853 issue = Issue.find(2)
1728 1854 @request.session[:user_id] = 2
1729 1855
1730 1856 put :update,
1731 1857 :id => issue.id,
1732 1858 :issue => {
1733 1859 :fixed_version_id => 4
1734 1860 },
1735 1861 :back_url => '/issues'
1736 1862
1737 1863 assert_response :redirect
1738 1864 assert_redirected_to '/issues'
1739 1865 end
1740 1866
1741 1867 def test_put_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1742 1868 issue = Issue.find(2)
1743 1869 @request.session[:user_id] = 2
1744 1870
1745 1871 put :update,
1746 1872 :id => issue.id,
1747 1873 :issue => {
1748 1874 :fixed_version_id => 4
1749 1875 },
1750 1876 :back_url => 'http://google.com'
1751 1877
1752 1878 assert_response :redirect
1753 1879 assert_redirected_to :controller => 'issues', :action => 'show', :id => issue.id
1754 1880 end
1755 1881
1756 1882 def test_get_bulk_edit
1757 1883 @request.session[:user_id] = 2
1758 1884 get :bulk_edit, :ids => [1, 2]
1759 1885 assert_response :success
1760 1886 assert_template 'bulk_edit'
1761 1887
1762 1888 assert_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1763 1889
1764 1890 # Project specific custom field, date type
1765 1891 field = CustomField.find(9)
1766 1892 assert !field.is_for_all?
1767 1893 assert_equal 'date', field.field_format
1768 1894 assert_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1769 1895
1770 1896 # System wide custom field
1771 1897 assert CustomField.find(1).is_for_all?
1772 1898 assert_tag :select, :attributes => {:name => 'issue[custom_field_values][1]'}
1773 1899
1774 1900 # Be sure we don't display inactive IssuePriorities
1775 1901 assert ! IssuePriority.find(15).active?
1776 1902 assert_no_tag :option, :attributes => {:value => '15'},
1777 1903 :parent => {:tag => 'select', :attributes => {:id => 'issue_priority_id'} }
1778 1904 end
1779 1905
1780 1906 def test_get_bulk_edit_on_different_projects
1781 1907 @request.session[:user_id] = 2
1782 1908 get :bulk_edit, :ids => [1, 2, 6]
1783 1909 assert_response :success
1784 1910 assert_template 'bulk_edit'
1785 1911
1786 1912 # Can not set issues from different projects as children of an issue
1787 1913 assert_no_tag :input, :attributes => {:name => 'issue[parent_issue_id]'}
1788 1914
1789 1915 # Project specific custom field, date type
1790 1916 field = CustomField.find(9)
1791 1917 assert !field.is_for_all?
1792 1918 assert !field.project_ids.include?(Issue.find(6).project_id)
1793 1919 assert_no_tag :input, :attributes => {:name => 'issue[custom_field_values][9]'}
1794 1920 end
1795 1921
1796 1922 def test_get_bulk_edit_with_user_custom_field
1797 1923 field = IssueCustomField.create!(:name => 'Tester', :field_format => 'user', :is_for_all => true)
1798 1924
1799 1925 @request.session[:user_id] = 2
1800 1926 get :bulk_edit, :ids => [1, 2]
1801 1927 assert_response :success
1802 1928 assert_template 'bulk_edit'
1803 1929
1804 1930 assert_tag :select,
1805 1931 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1806 1932 :children => {
1807 1933 :only => {:tag => 'option'},
1808 1934 :count => Project.find(1).users.count + 1
1809 1935 }
1810 1936 end
1811 1937
1812 1938 def test_get_bulk_edit_with_version_custom_field
1813 1939 field = IssueCustomField.create!(:name => 'Affected version', :field_format => 'version', :is_for_all => true)
1814 1940
1815 1941 @request.session[:user_id] = 2
1816 1942 get :bulk_edit, :ids => [1, 2]
1817 1943 assert_response :success
1818 1944 assert_template 'bulk_edit'
1819 1945
1820 1946 assert_tag :select,
1821 1947 :attributes => {:name => "issue[custom_field_values][#{field.id}]"},
1822 1948 :children => {
1823 1949 :only => {:tag => 'option'},
1824 1950 :count => Project.find(1).shared_versions.count + 1
1825 1951 }
1826 1952 end
1827 1953
1828 1954 def test_bulk_update
1829 1955 @request.session[:user_id] = 2
1830 1956 # update issues priority
1831 1957 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1832 1958 :issue => {:priority_id => 7,
1833 1959 :assigned_to_id => '',
1834 1960 :custom_field_values => {'2' => ''}}
1835 1961
1836 1962 assert_response 302
1837 1963 # check that the issues were updated
1838 1964 assert_equal [7, 7], Issue.find_all_by_id([1, 2]).collect {|i| i.priority.id}
1839 1965
1840 1966 issue = Issue.find(1)
1841 1967 journal = issue.journals.find(:first, :order => 'created_on DESC')
1842 1968 assert_equal '125', issue.custom_value_for(2).value
1843 1969 assert_equal 'Bulk editing', journal.notes
1844 1970 assert_equal 1, journal.details.size
1845 1971 end
1846 1972
1847 1973 def test_bulk_update_with_group_assignee
1848 1974 group = Group.find(11)
1849 1975 project = Project.find(1)
1850 1976 project.members << Member.new(:principal => group, :roles => [Role.first])
1851 1977
1852 1978 @request.session[:user_id] = 2
1853 1979 # update issues assignee
1854 1980 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing',
1855 1981 :issue => {:priority_id => '',
1856 1982 :assigned_to_id => group.id,
1857 1983 :custom_field_values => {'2' => ''}}
1858 1984
1859 1985 assert_response 302
1860 1986 assert_equal [group, group], Issue.find_all_by_id([1, 2]).collect {|i| i.assigned_to}
1861 1987 end
1862 1988
1863 1989 def test_bulk_update_on_different_projects
1864 1990 @request.session[:user_id] = 2
1865 1991 # update issues priority
1866 1992 post :bulk_update, :ids => [1, 2, 6], :notes => 'Bulk editing',
1867 1993 :issue => {:priority_id => 7,
1868 1994 :assigned_to_id => '',
1869 1995 :custom_field_values => {'2' => ''}}
1870 1996
1871 1997 assert_response 302
1872 1998 # check that the issues were updated
1873 1999 assert_equal [7, 7, 7], Issue.find([1,2,6]).map(&:priority_id)
1874 2000
1875 2001 issue = Issue.find(1)
1876 2002 journal = issue.journals.find(:first, :order => 'created_on DESC')
1877 2003 assert_equal '125', issue.custom_value_for(2).value
1878 2004 assert_equal 'Bulk editing', journal.notes
1879 2005 assert_equal 1, journal.details.size
1880 2006 end
1881 2007
1882 2008 def test_bulk_update_on_different_projects_without_rights
1883 2009 @request.session[:user_id] = 3
1884 2010 user = User.find(3)
1885 2011 action = { :controller => "issues", :action => "bulk_update" }
1886 2012 assert user.allowed_to?(action, Issue.find(1).project)
1887 2013 assert ! user.allowed_to?(action, Issue.find(6).project)
1888 2014 post :bulk_update, :ids => [1, 6], :notes => 'Bulk should fail',
1889 2015 :issue => {:priority_id => 7,
1890 2016 :assigned_to_id => '',
1891 2017 :custom_field_values => {'2' => ''}}
1892 2018 assert_response 403
1893 2019 assert_not_equal "Bulk should fail", Journal.last.notes
1894 2020 end
1895 2021
1896 2022 def test_bullk_update_should_send_a_notification
1897 2023 @request.session[:user_id] = 2
1898 2024 ActionMailer::Base.deliveries.clear
1899 2025 post(:bulk_update,
1900 2026 {
1901 2027 :ids => [1, 2],
1902 2028 :notes => 'Bulk editing',
1903 2029 :issue => {
1904 2030 :priority_id => 7,
1905 2031 :assigned_to_id => '',
1906 2032 :custom_field_values => {'2' => ''}
1907 2033 }
1908 2034 })
1909 2035
1910 2036 assert_response 302
1911 2037 assert_equal 2, ActionMailer::Base.deliveries.size
1912 2038 end
1913 2039
1914 2040 def test_bulk_update_status
1915 2041 @request.session[:user_id] = 2
1916 2042 # update issues priority
1917 2043 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing status',
1918 2044 :issue => {:priority_id => '',
1919 2045 :assigned_to_id => '',
1920 2046 :status_id => '5'}
1921 2047
1922 2048 assert_response 302
1923 2049 issue = Issue.find(1)
1924 2050 assert issue.closed?
1925 2051 end
1926 2052
1927 2053 def test_bulk_update_parent_id
1928 2054 @request.session[:user_id] = 2
1929 2055 post :bulk_update, :ids => [1, 3],
1930 2056 :notes => 'Bulk editing parent',
1931 2057 :issue => {:priority_id => '', :assigned_to_id => '', :status_id => '', :parent_issue_id => '2'}
1932 2058
1933 2059 assert_response 302
1934 2060 parent = Issue.find(2)
1935 2061 assert_equal parent.id, Issue.find(1).parent_id
1936 2062 assert_equal parent.id, Issue.find(3).parent_id
1937 2063 assert_equal [1, 3], parent.children.collect(&:id).sort
1938 2064 end
1939 2065
1940 2066 def test_bulk_update_custom_field
1941 2067 @request.session[:user_id] = 2
1942 2068 # update issues priority
1943 2069 post :bulk_update, :ids => [1, 2], :notes => 'Bulk editing custom field',
1944 2070 :issue => {:priority_id => '',
1945 2071 :assigned_to_id => '',
1946 2072 :custom_field_values => {'2' => '777'}}
1947 2073
1948 2074 assert_response 302
1949 2075
1950 2076 issue = Issue.find(1)
1951 2077 journal = issue.journals.find(:first, :order => 'created_on DESC')
1952 2078 assert_equal '777', issue.custom_value_for(2).value
1953 2079 assert_equal 1, journal.details.size
1954 2080 assert_equal '125', journal.details.first.old_value
1955 2081 assert_equal '777', journal.details.first.value
1956 2082 end
1957 2083
1958 2084 def test_bulk_update_unassign
1959 2085 assert_not_nil Issue.find(2).assigned_to
1960 2086 @request.session[:user_id] = 2
1961 2087 # unassign issues
1962 2088 post :bulk_update, :ids => [1, 2], :notes => 'Bulk unassigning', :issue => {:assigned_to_id => 'none'}
1963 2089 assert_response 302
1964 2090 # check that the issues were updated
1965 2091 assert_nil Issue.find(2).assigned_to
1966 2092 end
1967 2093
1968 2094 def test_post_bulk_update_should_allow_fixed_version_to_be_set_to_a_subproject
1969 2095 @request.session[:user_id] = 2
1970 2096
1971 2097 post :bulk_update, :ids => [1,2], :issue => {:fixed_version_id => 4}
1972 2098
1973 2099 assert_response :redirect
1974 2100 issues = Issue.find([1,2])
1975 2101 issues.each do |issue|
1976 2102 assert_equal 4, issue.fixed_version_id
1977 2103 assert_not_equal issue.project_id, issue.fixed_version.project_id
1978 2104 end
1979 2105 end
1980 2106
1981 2107 def test_post_bulk_update_should_redirect_back_using_the_back_url_parameter
1982 2108 @request.session[:user_id] = 2
1983 2109 post :bulk_update, :ids => [1,2], :back_url => '/issues'
1984 2110
1985 2111 assert_response :redirect
1986 2112 assert_redirected_to '/issues'
1987 2113 end
1988 2114
1989 2115 def test_post_bulk_update_should_not_redirect_back_using_the_back_url_parameter_off_the_host
1990 2116 @request.session[:user_id] = 2
1991 2117 post :bulk_update, :ids => [1,2], :back_url => 'http://google.com'
1992 2118
1993 2119 assert_response :redirect
1994 2120 assert_redirected_to :controller => 'issues', :action => 'index', :project_id => Project.find(1).identifier
1995 2121 end
1996 2122
1997 2123 def test_destroy_issue_with_no_time_entries
1998 2124 assert_nil TimeEntry.find_by_issue_id(2)
1999 2125 @request.session[:user_id] = 2
2000 2126 delete :destroy, :id => 2
2001 2127 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2002 2128 assert_nil Issue.find_by_id(2)
2003 2129 end
2004 2130
2005 2131 def test_destroy_issues_with_time_entries
2006 2132 @request.session[:user_id] = 2
2007 2133 delete :destroy, :ids => [1, 3]
2008 2134 assert_response :success
2009 2135 assert_template 'destroy'
2010 2136 assert_not_nil assigns(:hours)
2011 2137 assert Issue.find_by_id(1) && Issue.find_by_id(3)
2012 2138 end
2013 2139
2014 2140 def test_destroy_issues_and_destroy_time_entries
2015 2141 @request.session[:user_id] = 2
2016 2142 delete :destroy, :ids => [1, 3], :todo => 'destroy'
2017 2143 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2018 2144 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2019 2145 assert_nil TimeEntry.find_by_id([1, 2])
2020 2146 end
2021 2147
2022 2148 def test_destroy_issues_and_assign_time_entries_to_project
2023 2149 @request.session[:user_id] = 2
2024 2150 delete :destroy, :ids => [1, 3], :todo => 'nullify'
2025 2151 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2026 2152 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2027 2153 assert_nil TimeEntry.find(1).issue_id
2028 2154 assert_nil TimeEntry.find(2).issue_id
2029 2155 end
2030 2156
2031 2157 def test_destroy_issues_and_reassign_time_entries_to_another_issue
2032 2158 @request.session[:user_id] = 2
2033 2159 delete :destroy, :ids => [1, 3], :todo => 'reassign', :reassign_to_id => 2
2034 2160 assert_redirected_to :action => 'index', :project_id => 'ecookbook'
2035 2161 assert !(Issue.find_by_id(1) || Issue.find_by_id(3))
2036 2162 assert_equal 2, TimeEntry.find(1).issue_id
2037 2163 assert_equal 2, TimeEntry.find(2).issue_id
2038 2164 end
2039 2165
2040 2166 def test_destroy_issues_from_different_projects
2041 2167 @request.session[:user_id] = 2
2042 2168 delete :destroy, :ids => [1, 2, 6], :todo => 'destroy'
2043 2169 assert_redirected_to :controller => 'issues', :action => 'index'
2044 2170 assert !(Issue.find_by_id(1) || Issue.find_by_id(2) || Issue.find_by_id(6))
2045 2171 end
2046 2172
2047 2173 def test_destroy_parent_and_child_issues
2048 2174 parent = Issue.generate!(:project_id => 1, :tracker_id => 1)
2049 2175 child = Issue.generate!(:project_id => 1, :tracker_id => 1, :parent_issue_id => parent.id)
2050 2176 assert child.is_descendant_of?(parent.reload)
2051 2177
2052 2178 @request.session[:user_id] = 2
2053 2179 assert_difference 'Issue.count', -2 do
2054 2180 delete :destroy, :ids => [parent.id, child.id], :todo => 'destroy'
2055 2181 end
2056 2182 assert_response 302
2057 2183 end
2058 2184
2059 2185 def test_default_search_scope
2060 2186 get :index
2061 2187 assert_tag :div, :attributes => {:id => 'quick-search'},
2062 2188 :child => {:tag => 'form',
2063 2189 :child => {:tag => 'input', :attributes => {:name => 'issues', :type => 'hidden', :value => '1'}}}
2064 2190 end
2065 2191 end
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now