##// END OF EJS Templates
fix wrong issue 'assigned-to-my-group' css class (#16038)...
Toshi MARUYAMA -
r12569:a0baf7ee5d41
parent child
Show More
@@ -1,1566 +1,1566
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class Issue < ActiveRecord::Base
18 class Issue < ActiveRecord::Base
19 include Redmine::SafeAttributes
19 include Redmine::SafeAttributes
20 include Redmine::Utils::DateCalculation
20 include Redmine::Utils::DateCalculation
21 include Redmine::I18n
21 include Redmine::I18n
22
22
23 belongs_to :project
23 belongs_to :project
24 belongs_to :tracker
24 belongs_to :tracker
25 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
25 belongs_to :status, :class_name => 'IssueStatus', :foreign_key => 'status_id'
26 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
26 belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
27 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
27 belongs_to :assigned_to, :class_name => 'Principal', :foreign_key => 'assigned_to_id'
28 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
28 belongs_to :fixed_version, :class_name => 'Version', :foreign_key => 'fixed_version_id'
29 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
29 belongs_to :priority, :class_name => 'IssuePriority', :foreign_key => 'priority_id'
30 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
30 belongs_to :category, :class_name => 'IssueCategory', :foreign_key => 'category_id'
31
31
32 has_many :journals, :as => :journalized, :dependent => :destroy
32 has_many :journals, :as => :journalized, :dependent => :destroy
33 has_many :visible_journals,
33 has_many :visible_journals,
34 :class_name => 'Journal',
34 :class_name => 'Journal',
35 :as => :journalized,
35 :as => :journalized,
36 :conditions => Proc.new {
36 :conditions => Proc.new {
37 ["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
37 ["(#{Journal.table_name}.private_notes = ? OR (#{Project.allowed_to_condition(User.current, :view_private_notes)}))", false]
38 },
38 },
39 :readonly => true
39 :readonly => true
40
40
41 has_many :time_entries, :dependent => :destroy
41 has_many :time_entries, :dependent => :destroy
42 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
42 has_and_belongs_to_many :changesets, :order => "#{Changeset.table_name}.committed_on ASC, #{Changeset.table_name}.id ASC"
43
43
44 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
44 has_many :relations_from, :class_name => 'IssueRelation', :foreign_key => 'issue_from_id', :dependent => :delete_all
45 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
45 has_many :relations_to, :class_name => 'IssueRelation', :foreign_key => 'issue_to_id', :dependent => :delete_all
46
46
47 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
47 acts_as_nested_set :scope => 'root_id', :dependent => :destroy
48 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
48 acts_as_attachable :after_add => :attachment_added, :after_remove => :attachment_removed
49 acts_as_customizable
49 acts_as_customizable
50 acts_as_watchable
50 acts_as_watchable
51 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
51 acts_as_searchable :columns => ['subject', "#{table_name}.description", "#{Journal.table_name}.notes"],
52 :include => [:project, :visible_journals],
52 :include => [:project, :visible_journals],
53 # sort by id so that limited eager loading doesn't break with postgresql
53 # sort by id so that limited eager loading doesn't break with postgresql
54 :order_column => "#{table_name}.id"
54 :order_column => "#{table_name}.id"
55 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
55 acts_as_event :title => Proc.new {|o| "#{o.tracker.name} ##{o.id} (#{o.status}): #{o.subject}"},
56 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
56 :url => Proc.new {|o| {:controller => 'issues', :action => 'show', :id => o.id}},
57 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
57 :type => Proc.new {|o| 'issue' + (o.closed? ? ' closed' : '') }
58
58
59 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
59 acts_as_activity_provider :find_options => {:include => [:project, :author, :tracker]},
60 :author_key => :author_id
60 :author_key => :author_id
61
61
62 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
62 DONE_RATIO_OPTIONS = %w(issue_field issue_status)
63
63
64 attr_reader :current_journal
64 attr_reader :current_journal
65 delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
65 delegate :notes, :notes=, :private_notes, :private_notes=, :to => :current_journal, :allow_nil => true
66
66
67 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
67 validates_presence_of :subject, :priority, :project, :tracker, :author, :status
68
68
69 validates_length_of :subject, :maximum => 255
69 validates_length_of :subject, :maximum => 255
70 validates_inclusion_of :done_ratio, :in => 0..100
70 validates_inclusion_of :done_ratio, :in => 0..100
71 validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
71 validates :estimated_hours, :numericality => {:greater_than_or_equal_to => 0, :allow_nil => true, :message => :invalid}
72 validates :start_date, :date => true
72 validates :start_date, :date => true
73 validates :due_date, :date => true
73 validates :due_date, :date => true
74 validate :validate_issue, :validate_required_fields
74 validate :validate_issue, :validate_required_fields
75
75
76 scope :visible, lambda {|*args|
76 scope :visible, lambda {|*args|
77 includes(:project).where(Issue.visible_condition(args.shift || User.current, *args))
77 includes(:project).where(Issue.visible_condition(args.shift || User.current, *args))
78 }
78 }
79
79
80 scope :open, lambda {|*args|
80 scope :open, lambda {|*args|
81 is_closed = args.size > 0 ? !args.first : false
81 is_closed = args.size > 0 ? !args.first : false
82 includes(:status).where("#{IssueStatus.table_name}.is_closed = ?", is_closed)
82 includes(:status).where("#{IssueStatus.table_name}.is_closed = ?", is_closed)
83 }
83 }
84
84
85 scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") }
85 scope :recently_updated, lambda { order("#{Issue.table_name}.updated_on DESC") }
86 scope :on_active_project, lambda {
86 scope :on_active_project, lambda {
87 includes(:status, :project, :tracker).where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE)
87 includes(:status, :project, :tracker).where("#{Project.table_name}.status = ?", Project::STATUS_ACTIVE)
88 }
88 }
89 scope :fixed_version, lambda {|versions|
89 scope :fixed_version, lambda {|versions|
90 ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v}
90 ids = [versions].flatten.compact.map {|v| v.is_a?(Version) ? v.id : v}
91 ids.any? ? where(:fixed_version_id => ids) : where('1=0')
91 ids.any? ? where(:fixed_version_id => ids) : where('1=0')
92 }
92 }
93
93
94 before_create :default_assign
94 before_create :default_assign
95 before_save :close_duplicates, :update_done_ratio_from_issue_status,
95 before_save :close_duplicates, :update_done_ratio_from_issue_status,
96 :force_updated_on_change, :update_closed_on, :set_assigned_to_was
96 :force_updated_on_change, :update_closed_on, :set_assigned_to_was
97 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
97 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
98 after_save :reschedule_following_issues, :update_nested_set_attributes,
98 after_save :reschedule_following_issues, :update_nested_set_attributes,
99 :update_parent_attributes, :create_journal
99 :update_parent_attributes, :create_journal
100 # Should be after_create but would be called before previous after_save callbacks
100 # Should be after_create but would be called before previous after_save callbacks
101 after_save :after_create_from_copy
101 after_save :after_create_from_copy
102 after_destroy :update_parent_attributes
102 after_destroy :update_parent_attributes
103 after_create :send_notification
103 after_create :send_notification
104 # Keep it at the end of after_save callbacks
104 # Keep it at the end of after_save callbacks
105 after_save :clear_assigned_to_was
105 after_save :clear_assigned_to_was
106
106
107 # Returns a SQL conditions string used to find all issues visible by the specified user
107 # Returns a SQL conditions string used to find all issues visible by the specified user
108 def self.visible_condition(user, options={})
108 def self.visible_condition(user, options={})
109 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
109 Project.allowed_to_condition(user, :view_issues, options) do |role, user|
110 if user.logged?
110 if user.logged?
111 case role.issues_visibility
111 case role.issues_visibility
112 when 'all'
112 when 'all'
113 nil
113 nil
114 when 'default'
114 when 'default'
115 user_ids = [user.id] + user.groups.map(&:id).compact
115 user_ids = [user.id] + user.groups.map(&:id).compact
116 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
116 "(#{table_name}.is_private = #{connection.quoted_false} OR #{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
117 when 'own'
117 when 'own'
118 user_ids = [user.id] + user.groups.map(&:id).compact
118 user_ids = [user.id] + user.groups.map(&:id).compact
119 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
119 "(#{table_name}.author_id = #{user.id} OR #{table_name}.assigned_to_id IN (#{user_ids.join(',')}))"
120 else
120 else
121 '1=0'
121 '1=0'
122 end
122 end
123 else
123 else
124 "(#{table_name}.is_private = #{connection.quoted_false})"
124 "(#{table_name}.is_private = #{connection.quoted_false})"
125 end
125 end
126 end
126 end
127 end
127 end
128
128
129 # Returns true if usr or current user is allowed to view the issue
129 # Returns true if usr or current user is allowed to view the issue
130 def visible?(usr=nil)
130 def visible?(usr=nil)
131 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
131 (usr || User.current).allowed_to?(:view_issues, self.project) do |role, user|
132 if user.logged?
132 if user.logged?
133 case role.issues_visibility
133 case role.issues_visibility
134 when 'all'
134 when 'all'
135 true
135 true
136 when 'default'
136 when 'default'
137 !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
137 !self.is_private? || (self.author == user || user.is_or_belongs_to?(assigned_to))
138 when 'own'
138 when 'own'
139 self.author == user || user.is_or_belongs_to?(assigned_to)
139 self.author == user || user.is_or_belongs_to?(assigned_to)
140 else
140 else
141 false
141 false
142 end
142 end
143 else
143 else
144 !self.is_private?
144 !self.is_private?
145 end
145 end
146 end
146 end
147 end
147 end
148
148
149 # Returns true if user or current user is allowed to edit or add a note to the issue
149 # Returns true if user or current user is allowed to edit or add a note to the issue
150 def editable?(user=User.current)
150 def editable?(user=User.current)
151 user.allowed_to?(:edit_issues, project) || user.allowed_to?(:add_issue_notes, project)
151 user.allowed_to?(:edit_issues, project) || user.allowed_to?(:add_issue_notes, project)
152 end
152 end
153
153
154 def initialize(attributes=nil, *args)
154 def initialize(attributes=nil, *args)
155 super
155 super
156 if new_record?
156 if new_record?
157 # set default values for new records only
157 # set default values for new records only
158 self.status ||= IssueStatus.default
158 self.status ||= IssueStatus.default
159 self.priority ||= IssuePriority.default
159 self.priority ||= IssuePriority.default
160 self.watcher_user_ids = []
160 self.watcher_user_ids = []
161 end
161 end
162 end
162 end
163
163
164 def create_or_update
164 def create_or_update
165 super
165 super
166 ensure
166 ensure
167 @status_was = nil
167 @status_was = nil
168 end
168 end
169 private :create_or_update
169 private :create_or_update
170
170
171 # AR#Persistence#destroy would raise and RecordNotFound exception
171 # AR#Persistence#destroy would raise and RecordNotFound exception
172 # if the issue was already deleted or updated (non matching lock_version).
172 # if the issue was already deleted or updated (non matching lock_version).
173 # This is a problem when bulk deleting issues or deleting a project
173 # This is a problem when bulk deleting issues or deleting a project
174 # (because an issue may already be deleted if its parent was deleted
174 # (because an issue may already be deleted if its parent was deleted
175 # first).
175 # first).
176 # The issue is reloaded by the nested_set before being deleted so
176 # The issue is reloaded by the nested_set before being deleted so
177 # the lock_version condition should not be an issue but we handle it.
177 # the lock_version condition should not be an issue but we handle it.
178 def destroy
178 def destroy
179 super
179 super
180 rescue ActiveRecord::RecordNotFound
180 rescue ActiveRecord::RecordNotFound
181 # Stale or already deleted
181 # Stale or already deleted
182 begin
182 begin
183 reload
183 reload
184 rescue ActiveRecord::RecordNotFound
184 rescue ActiveRecord::RecordNotFound
185 # The issue was actually already deleted
185 # The issue was actually already deleted
186 @destroyed = true
186 @destroyed = true
187 return freeze
187 return freeze
188 end
188 end
189 # The issue was stale, retry to destroy
189 # The issue was stale, retry to destroy
190 super
190 super
191 end
191 end
192
192
193 alias :base_reload :reload
193 alias :base_reload :reload
194 def reload(*args)
194 def reload(*args)
195 @workflow_rule_by_attribute = nil
195 @workflow_rule_by_attribute = nil
196 @assignable_versions = nil
196 @assignable_versions = nil
197 @relations = nil
197 @relations = nil
198 base_reload(*args)
198 base_reload(*args)
199 end
199 end
200
200
201 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
201 # Overrides Redmine::Acts::Customizable::InstanceMethods#available_custom_fields
202 def available_custom_fields
202 def available_custom_fields
203 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
203 (project && tracker) ? (project.all_issue_custom_fields & tracker.custom_fields.all) : []
204 end
204 end
205
205
206 def visible_custom_field_values(user=nil)
206 def visible_custom_field_values(user=nil)
207 user_real = user || User.current
207 user_real = user || User.current
208 custom_field_values.select do |value|
208 custom_field_values.select do |value|
209 value.custom_field.visible_by?(project, user_real)
209 value.custom_field.visible_by?(project, user_real)
210 end
210 end
211 end
211 end
212
212
213 # Copies attributes from another issue, arg can be an id or an Issue
213 # Copies attributes from another issue, arg can be an id or an Issue
214 def copy_from(arg, options={})
214 def copy_from(arg, options={})
215 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
215 issue = arg.is_a?(Issue) ? arg : Issue.visible.find(arg)
216 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
216 self.attributes = issue.attributes.dup.except("id", "root_id", "parent_id", "lft", "rgt", "created_on", "updated_on")
217 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
217 self.custom_field_values = issue.custom_field_values.inject({}) {|h,v| h[v.custom_field_id] = v.value; h}
218 self.status = issue.status
218 self.status = issue.status
219 self.author = User.current
219 self.author = User.current
220 unless options[:attachments] == false
220 unless options[:attachments] == false
221 self.attachments = issue.attachments.map do |attachement|
221 self.attachments = issue.attachments.map do |attachement|
222 attachement.copy(:container => self)
222 attachement.copy(:container => self)
223 end
223 end
224 end
224 end
225 @copied_from = issue
225 @copied_from = issue
226 @copy_options = options
226 @copy_options = options
227 self
227 self
228 end
228 end
229
229
230 # Returns an unsaved copy of the issue
230 # Returns an unsaved copy of the issue
231 def copy(attributes=nil, copy_options={})
231 def copy(attributes=nil, copy_options={})
232 copy = self.class.new.copy_from(self, copy_options)
232 copy = self.class.new.copy_from(self, copy_options)
233 copy.attributes = attributes if attributes
233 copy.attributes = attributes if attributes
234 copy
234 copy
235 end
235 end
236
236
237 # Returns true if the issue is a copy
237 # Returns true if the issue is a copy
238 def copy?
238 def copy?
239 @copied_from.present?
239 @copied_from.present?
240 end
240 end
241
241
242 # Moves/copies an issue to a new project and tracker
242 # Moves/copies an issue to a new project and tracker
243 # Returns the moved/copied issue on success, false on failure
243 # Returns the moved/copied issue on success, false on failure
244 def move_to_project(new_project, new_tracker=nil, options={})
244 def move_to_project(new_project, new_tracker=nil, options={})
245 ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead."
245 ActiveSupport::Deprecation.warn "Issue#move_to_project is deprecated, use #project= instead."
246
246
247 if options[:copy]
247 if options[:copy]
248 issue = self.copy
248 issue = self.copy
249 else
249 else
250 issue = self
250 issue = self
251 end
251 end
252
252
253 issue.init_journal(User.current, options[:notes])
253 issue.init_journal(User.current, options[:notes])
254
254
255 # Preserve previous behaviour
255 # Preserve previous behaviour
256 # #move_to_project doesn't change tracker automatically
256 # #move_to_project doesn't change tracker automatically
257 issue.send :project=, new_project, true
257 issue.send :project=, new_project, true
258 if new_tracker
258 if new_tracker
259 issue.tracker = new_tracker
259 issue.tracker = new_tracker
260 end
260 end
261 # Allow bulk setting of attributes on the issue
261 # Allow bulk setting of attributes on the issue
262 if options[:attributes]
262 if options[:attributes]
263 issue.attributes = options[:attributes]
263 issue.attributes = options[:attributes]
264 end
264 end
265
265
266 issue.save ? issue : false
266 issue.save ? issue : false
267 end
267 end
268
268
269 def status_id=(sid)
269 def status_id=(sid)
270 self.status = nil
270 self.status = nil
271 result = write_attribute(:status_id, sid)
271 result = write_attribute(:status_id, sid)
272 @workflow_rule_by_attribute = nil
272 @workflow_rule_by_attribute = nil
273 result
273 result
274 end
274 end
275
275
276 def priority_id=(pid)
276 def priority_id=(pid)
277 self.priority = nil
277 self.priority = nil
278 write_attribute(:priority_id, pid)
278 write_attribute(:priority_id, pid)
279 end
279 end
280
280
281 def category_id=(cid)
281 def category_id=(cid)
282 self.category = nil
282 self.category = nil
283 write_attribute(:category_id, cid)
283 write_attribute(:category_id, cid)
284 end
284 end
285
285
286 def fixed_version_id=(vid)
286 def fixed_version_id=(vid)
287 self.fixed_version = nil
287 self.fixed_version = nil
288 write_attribute(:fixed_version_id, vid)
288 write_attribute(:fixed_version_id, vid)
289 end
289 end
290
290
291 def tracker_id=(tid)
291 def tracker_id=(tid)
292 self.tracker = nil
292 self.tracker = nil
293 result = write_attribute(:tracker_id, tid)
293 result = write_attribute(:tracker_id, tid)
294 @custom_field_values = nil
294 @custom_field_values = nil
295 @workflow_rule_by_attribute = nil
295 @workflow_rule_by_attribute = nil
296 result
296 result
297 end
297 end
298
298
299 def project_id=(project_id)
299 def project_id=(project_id)
300 if project_id.to_s != self.project_id.to_s
300 if project_id.to_s != self.project_id.to_s
301 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
301 self.project = (project_id.present? ? Project.find_by_id(project_id) : nil)
302 end
302 end
303 end
303 end
304
304
305 def project=(project, keep_tracker=false)
305 def project=(project, keep_tracker=false)
306 project_was = self.project
306 project_was = self.project
307 write_attribute(:project_id, project ? project.id : nil)
307 write_attribute(:project_id, project ? project.id : nil)
308 association_instance_set('project', project)
308 association_instance_set('project', project)
309 if project_was && project && project_was != project
309 if project_was && project && project_was != project
310 @assignable_versions = nil
310 @assignable_versions = nil
311
311
312 unless keep_tracker || project.trackers.include?(tracker)
312 unless keep_tracker || project.trackers.include?(tracker)
313 self.tracker = project.trackers.first
313 self.tracker = project.trackers.first
314 end
314 end
315 # Reassign to the category with same name if any
315 # Reassign to the category with same name if any
316 if category
316 if category
317 self.category = project.issue_categories.find_by_name(category.name)
317 self.category = project.issue_categories.find_by_name(category.name)
318 end
318 end
319 # Keep the fixed_version if it's still valid in the new_project
319 # Keep the fixed_version if it's still valid in the new_project
320 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
320 if fixed_version && fixed_version.project != project && !project.shared_versions.include?(fixed_version)
321 self.fixed_version = nil
321 self.fixed_version = nil
322 end
322 end
323 # Clear the parent task if it's no longer valid
323 # Clear the parent task if it's no longer valid
324 unless valid_parent_project?
324 unless valid_parent_project?
325 self.parent_issue_id = nil
325 self.parent_issue_id = nil
326 end
326 end
327 @custom_field_values = nil
327 @custom_field_values = nil
328 end
328 end
329 end
329 end
330
330
331 def description=(arg)
331 def description=(arg)
332 if arg.is_a?(String)
332 if arg.is_a?(String)
333 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
333 arg = arg.gsub(/(\r\n|\n|\r)/, "\r\n")
334 end
334 end
335 write_attribute(:description, arg)
335 write_attribute(:description, arg)
336 end
336 end
337
337
338 # Overrides assign_attributes so that project and tracker get assigned first
338 # Overrides assign_attributes so that project and tracker get assigned first
339 def assign_attributes_with_project_and_tracker_first(new_attributes, *args)
339 def assign_attributes_with_project_and_tracker_first(new_attributes, *args)
340 return if new_attributes.nil?
340 return if new_attributes.nil?
341 attrs = new_attributes.dup
341 attrs = new_attributes.dup
342 attrs.stringify_keys!
342 attrs.stringify_keys!
343
343
344 %w(project project_id tracker tracker_id).each do |attr|
344 %w(project project_id tracker tracker_id).each do |attr|
345 if attrs.has_key?(attr)
345 if attrs.has_key?(attr)
346 send "#{attr}=", attrs.delete(attr)
346 send "#{attr}=", attrs.delete(attr)
347 end
347 end
348 end
348 end
349 send :assign_attributes_without_project_and_tracker_first, attrs, *args
349 send :assign_attributes_without_project_and_tracker_first, attrs, *args
350 end
350 end
351 # Do not redefine alias chain on reload (see #4838)
351 # Do not redefine alias chain on reload (see #4838)
352 alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first)
352 alias_method_chain(:assign_attributes, :project_and_tracker_first) unless method_defined?(:assign_attributes_without_project_and_tracker_first)
353
353
354 def estimated_hours=(h)
354 def estimated_hours=(h)
355 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
355 write_attribute :estimated_hours, (h.is_a?(String) ? h.to_hours : h)
356 end
356 end
357
357
358 safe_attributes 'project_id',
358 safe_attributes 'project_id',
359 :if => lambda {|issue, user|
359 :if => lambda {|issue, user|
360 if issue.new_record?
360 if issue.new_record?
361 issue.copy?
361 issue.copy?
362 elsif user.allowed_to?(:move_issues, issue.project)
362 elsif user.allowed_to?(:move_issues, issue.project)
363 Issue.allowed_target_projects_on_move.count > 1
363 Issue.allowed_target_projects_on_move.count > 1
364 end
364 end
365 }
365 }
366
366
367 safe_attributes 'tracker_id',
367 safe_attributes 'tracker_id',
368 'status_id',
368 'status_id',
369 'category_id',
369 'category_id',
370 'assigned_to_id',
370 'assigned_to_id',
371 'priority_id',
371 'priority_id',
372 'fixed_version_id',
372 'fixed_version_id',
373 'subject',
373 'subject',
374 'description',
374 'description',
375 'start_date',
375 'start_date',
376 'due_date',
376 'due_date',
377 'done_ratio',
377 'done_ratio',
378 'estimated_hours',
378 'estimated_hours',
379 'custom_field_values',
379 'custom_field_values',
380 'custom_fields',
380 'custom_fields',
381 'lock_version',
381 'lock_version',
382 'notes',
382 'notes',
383 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
383 :if => lambda {|issue, user| issue.new_record? || user.allowed_to?(:edit_issues, issue.project) }
384
384
385 safe_attributes 'status_id',
385 safe_attributes 'status_id',
386 'assigned_to_id',
386 'assigned_to_id',
387 'fixed_version_id',
387 'fixed_version_id',
388 'done_ratio',
388 'done_ratio',
389 'lock_version',
389 'lock_version',
390 'notes',
390 'notes',
391 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
391 :if => lambda {|issue, user| issue.new_statuses_allowed_to(user).any? }
392
392
393 safe_attributes 'notes',
393 safe_attributes 'notes',
394 :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
394 :if => lambda {|issue, user| user.allowed_to?(:add_issue_notes, issue.project)}
395
395
396 safe_attributes 'private_notes',
396 safe_attributes 'private_notes',
397 :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
397 :if => lambda {|issue, user| !issue.new_record? && user.allowed_to?(:set_notes_private, issue.project)}
398
398
399 safe_attributes 'watcher_user_ids',
399 safe_attributes 'watcher_user_ids',
400 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
400 :if => lambda {|issue, user| issue.new_record? && user.allowed_to?(:add_issue_watchers, issue.project)}
401
401
402 safe_attributes 'is_private',
402 safe_attributes 'is_private',
403 :if => lambda {|issue, user|
403 :if => lambda {|issue, user|
404 user.allowed_to?(:set_issues_private, issue.project) ||
404 user.allowed_to?(:set_issues_private, issue.project) ||
405 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
405 (issue.author == user && user.allowed_to?(:set_own_issues_private, issue.project))
406 }
406 }
407
407
408 safe_attributes 'parent_issue_id',
408 safe_attributes 'parent_issue_id',
409 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
409 :if => lambda {|issue, user| (issue.new_record? || user.allowed_to?(:edit_issues, issue.project)) &&
410 user.allowed_to?(:manage_subtasks, issue.project)}
410 user.allowed_to?(:manage_subtasks, issue.project)}
411
411
412 def safe_attribute_names(user=nil)
412 def safe_attribute_names(user=nil)
413 names = super
413 names = super
414 names -= disabled_core_fields
414 names -= disabled_core_fields
415 names -= read_only_attribute_names(user)
415 names -= read_only_attribute_names(user)
416 names
416 names
417 end
417 end
418
418
419 # Safely sets attributes
419 # Safely sets attributes
420 # Should be called from controllers instead of #attributes=
420 # Should be called from controllers instead of #attributes=
421 # attr_accessible is too rough because we still want things like
421 # attr_accessible is too rough because we still want things like
422 # Issue.new(:project => foo) to work
422 # Issue.new(:project => foo) to work
423 def safe_attributes=(attrs, user=User.current)
423 def safe_attributes=(attrs, user=User.current)
424 return unless attrs.is_a?(Hash)
424 return unless attrs.is_a?(Hash)
425
425
426 attrs = attrs.dup
426 attrs = attrs.dup
427
427
428 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
428 # Project and Tracker must be set before since new_statuses_allowed_to depends on it.
429 if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
429 if (p = attrs.delete('project_id')) && safe_attribute?('project_id')
430 if allowed_target_projects(user).where(:id => p.to_i).exists?
430 if allowed_target_projects(user).where(:id => p.to_i).exists?
431 self.project_id = p
431 self.project_id = p
432 end
432 end
433 end
433 end
434
434
435 if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
435 if (t = attrs.delete('tracker_id')) && safe_attribute?('tracker_id')
436 self.tracker_id = t
436 self.tracker_id = t
437 end
437 end
438
438
439 if (s = attrs.delete('status_id')) && safe_attribute?('status_id')
439 if (s = attrs.delete('status_id')) && safe_attribute?('status_id')
440 if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i)
440 if new_statuses_allowed_to(user).collect(&:id).include?(s.to_i)
441 self.status_id = s
441 self.status_id = s
442 end
442 end
443 end
443 end
444
444
445 attrs = delete_unsafe_attributes(attrs, user)
445 attrs = delete_unsafe_attributes(attrs, user)
446 return if attrs.empty?
446 return if attrs.empty?
447
447
448 unless leaf?
448 unless leaf?
449 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
449 attrs.reject! {|k,v| %w(priority_id done_ratio start_date due_date estimated_hours).include?(k)}
450 end
450 end
451
451
452 if attrs['parent_issue_id'].present?
452 if attrs['parent_issue_id'].present?
453 s = attrs['parent_issue_id'].to_s
453 s = attrs['parent_issue_id'].to_s
454 unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1]))
454 unless (m = s.match(%r{\A#?(\d+)\z})) && (m[1] == parent_id.to_s || Issue.visible(user).exists?(m[1]))
455 @invalid_parent_issue_id = attrs.delete('parent_issue_id')
455 @invalid_parent_issue_id = attrs.delete('parent_issue_id')
456 end
456 end
457 end
457 end
458
458
459 if attrs['custom_field_values'].present?
459 if attrs['custom_field_values'].present?
460 editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
460 editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
461 # TODO: use #select when ruby1.8 support is dropped
461 # TODO: use #select when ruby1.8 support is dropped
462 attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| !editable_custom_field_ids.include?(k.to_s)}
462 attrs['custom_field_values'] = attrs['custom_field_values'].reject {|k, v| !editable_custom_field_ids.include?(k.to_s)}
463 end
463 end
464
464
465 if attrs['custom_fields'].present?
465 if attrs['custom_fields'].present?
466 editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
466 editable_custom_field_ids = editable_custom_field_values(user).map {|v| v.custom_field_id.to_s}
467 # TODO: use #select when ruby1.8 support is dropped
467 # TODO: use #select when ruby1.8 support is dropped
468 attrs['custom_fields'] = attrs['custom_fields'].reject {|c| !editable_custom_field_ids.include?(c['id'].to_s)}
468 attrs['custom_fields'] = attrs['custom_fields'].reject {|c| !editable_custom_field_ids.include?(c['id'].to_s)}
469 end
469 end
470
470
471 # mass-assignment security bypass
471 # mass-assignment security bypass
472 assign_attributes attrs, :without_protection => true
472 assign_attributes attrs, :without_protection => true
473 end
473 end
474
474
475 def disabled_core_fields
475 def disabled_core_fields
476 tracker ? tracker.disabled_core_fields : []
476 tracker ? tracker.disabled_core_fields : []
477 end
477 end
478
478
479 # Returns the custom_field_values that can be edited by the given user
479 # Returns the custom_field_values that can be edited by the given user
480 def editable_custom_field_values(user=nil)
480 def editable_custom_field_values(user=nil)
481 visible_custom_field_values(user).reject do |value|
481 visible_custom_field_values(user).reject do |value|
482 read_only_attribute_names(user).include?(value.custom_field_id.to_s)
482 read_only_attribute_names(user).include?(value.custom_field_id.to_s)
483 end
483 end
484 end
484 end
485
485
486 # Returns the names of attributes that are read-only for user or the current user
486 # Returns the names of attributes that are read-only for user or the current user
487 # For users with multiple roles, the read-only fields are the intersection of
487 # For users with multiple roles, the read-only fields are the intersection of
488 # read-only fields of each role
488 # read-only fields of each role
489 # The result is an array of strings where sustom fields are represented with their ids
489 # The result is an array of strings where sustom fields are represented with their ids
490 #
490 #
491 # Examples:
491 # Examples:
492 # issue.read_only_attribute_names # => ['due_date', '2']
492 # issue.read_only_attribute_names # => ['due_date', '2']
493 # issue.read_only_attribute_names(user) # => []
493 # issue.read_only_attribute_names(user) # => []
494 def read_only_attribute_names(user=nil)
494 def read_only_attribute_names(user=nil)
495 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys
495 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'readonly'}.keys
496 end
496 end
497
497
498 # Returns the names of required attributes for user or the current user
498 # Returns the names of required attributes for user or the current user
499 # For users with multiple roles, the required fields are the intersection of
499 # For users with multiple roles, the required fields are the intersection of
500 # required fields of each role
500 # required fields of each role
501 # The result is an array of strings where sustom fields are represented with their ids
501 # The result is an array of strings where sustom fields are represented with their ids
502 #
502 #
503 # Examples:
503 # Examples:
504 # issue.required_attribute_names # => ['due_date', '2']
504 # issue.required_attribute_names # => ['due_date', '2']
505 # issue.required_attribute_names(user) # => []
505 # issue.required_attribute_names(user) # => []
506 def required_attribute_names(user=nil)
506 def required_attribute_names(user=nil)
507 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys
507 workflow_rule_by_attribute(user).reject {|attr, rule| rule != 'required'}.keys
508 end
508 end
509
509
510 # Returns true if the attribute is required for user
510 # Returns true if the attribute is required for user
511 def required_attribute?(name, user=nil)
511 def required_attribute?(name, user=nil)
512 required_attribute_names(user).include?(name.to_s)
512 required_attribute_names(user).include?(name.to_s)
513 end
513 end
514
514
515 # Returns a hash of the workflow rule by attribute for the given user
515 # Returns a hash of the workflow rule by attribute for the given user
516 #
516 #
517 # Examples:
517 # Examples:
518 # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'}
518 # issue.workflow_rule_by_attribute # => {'due_date' => 'required', 'start_date' => 'readonly'}
519 def workflow_rule_by_attribute(user=nil)
519 def workflow_rule_by_attribute(user=nil)
520 return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil?
520 return @workflow_rule_by_attribute if @workflow_rule_by_attribute && user.nil?
521
521
522 user_real = user || User.current
522 user_real = user || User.current
523 roles = user_real.admin ? Role.all : user_real.roles_for_project(project)
523 roles = user_real.admin ? Role.all : user_real.roles_for_project(project)
524 return {} if roles.empty?
524 return {} if roles.empty?
525
525
526 result = {}
526 result = {}
527 workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all
527 workflow_permissions = WorkflowPermission.where(:tracker_id => tracker_id, :old_status_id => status_id, :role_id => roles.map(&:id)).all
528 if workflow_permissions.any?
528 if workflow_permissions.any?
529 workflow_rules = workflow_permissions.inject({}) do |h, wp|
529 workflow_rules = workflow_permissions.inject({}) do |h, wp|
530 h[wp.field_name] ||= []
530 h[wp.field_name] ||= []
531 h[wp.field_name] << wp.rule
531 h[wp.field_name] << wp.rule
532 h
532 h
533 end
533 end
534 workflow_rules.each do |attr, rules|
534 workflow_rules.each do |attr, rules|
535 next if rules.size < roles.size
535 next if rules.size < roles.size
536 uniq_rules = rules.uniq
536 uniq_rules = rules.uniq
537 if uniq_rules.size == 1
537 if uniq_rules.size == 1
538 result[attr] = uniq_rules.first
538 result[attr] = uniq_rules.first
539 else
539 else
540 result[attr] = 'required'
540 result[attr] = 'required'
541 end
541 end
542 end
542 end
543 end
543 end
544 @workflow_rule_by_attribute = result if user.nil?
544 @workflow_rule_by_attribute = result if user.nil?
545 result
545 result
546 end
546 end
547 private :workflow_rule_by_attribute
547 private :workflow_rule_by_attribute
548
548
549 def done_ratio
549 def done_ratio
550 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
550 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
551 status.default_done_ratio
551 status.default_done_ratio
552 else
552 else
553 read_attribute(:done_ratio)
553 read_attribute(:done_ratio)
554 end
554 end
555 end
555 end
556
556
557 def self.use_status_for_done_ratio?
557 def self.use_status_for_done_ratio?
558 Setting.issue_done_ratio == 'issue_status'
558 Setting.issue_done_ratio == 'issue_status'
559 end
559 end
560
560
561 def self.use_field_for_done_ratio?
561 def self.use_field_for_done_ratio?
562 Setting.issue_done_ratio == 'issue_field'
562 Setting.issue_done_ratio == 'issue_field'
563 end
563 end
564
564
565 def validate_issue
565 def validate_issue
566 if due_date && start_date && (start_date_changed? || due_date_changed?) && due_date < start_date
566 if due_date && start_date && (start_date_changed? || due_date_changed?) && due_date < start_date
567 errors.add :due_date, :greater_than_start_date
567 errors.add :due_date, :greater_than_start_date
568 end
568 end
569
569
570 if start_date && start_date_changed? && soonest_start && start_date < soonest_start
570 if start_date && start_date_changed? && soonest_start && start_date < soonest_start
571 errors.add :start_date, :earlier_than_minimum_start_date, :date => format_date(soonest_start)
571 errors.add :start_date, :earlier_than_minimum_start_date, :date => format_date(soonest_start)
572 end
572 end
573
573
574 if fixed_version
574 if fixed_version
575 if !assignable_versions.include?(fixed_version)
575 if !assignable_versions.include?(fixed_version)
576 errors.add :fixed_version_id, :inclusion
576 errors.add :fixed_version_id, :inclusion
577 elsif reopened? && fixed_version.closed?
577 elsif reopened? && fixed_version.closed?
578 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
578 errors.add :base, I18n.t(:error_can_not_reopen_issue_on_closed_version)
579 end
579 end
580 end
580 end
581
581
582 # Checks that the issue can not be added/moved to a disabled tracker
582 # Checks that the issue can not be added/moved to a disabled tracker
583 if project && (tracker_id_changed? || project_id_changed?)
583 if project && (tracker_id_changed? || project_id_changed?)
584 unless project.trackers.include?(tracker)
584 unless project.trackers.include?(tracker)
585 errors.add :tracker_id, :inclusion
585 errors.add :tracker_id, :inclusion
586 end
586 end
587 end
587 end
588
588
589 # Checks parent issue assignment
589 # Checks parent issue assignment
590 if @invalid_parent_issue_id.present?
590 if @invalid_parent_issue_id.present?
591 errors.add :parent_issue_id, :invalid
591 errors.add :parent_issue_id, :invalid
592 elsif @parent_issue
592 elsif @parent_issue
593 if !valid_parent_project?(@parent_issue)
593 if !valid_parent_project?(@parent_issue)
594 errors.add :parent_issue_id, :invalid
594 errors.add :parent_issue_id, :invalid
595 elsif (@parent_issue != parent) && (all_dependent_issues.include?(@parent_issue) || @parent_issue.all_dependent_issues.include?(self))
595 elsif (@parent_issue != parent) && (all_dependent_issues.include?(@parent_issue) || @parent_issue.all_dependent_issues.include?(self))
596 errors.add :parent_issue_id, :invalid
596 errors.add :parent_issue_id, :invalid
597 elsif !new_record?
597 elsif !new_record?
598 # moving an existing issue
598 # moving an existing issue
599 if @parent_issue.root_id != root_id
599 if @parent_issue.root_id != root_id
600 # we can always move to another tree
600 # we can always move to another tree
601 elsif move_possible?(@parent_issue)
601 elsif move_possible?(@parent_issue)
602 # move accepted inside tree
602 # move accepted inside tree
603 else
603 else
604 errors.add :parent_issue_id, :invalid
604 errors.add :parent_issue_id, :invalid
605 end
605 end
606 end
606 end
607 end
607 end
608 end
608 end
609
609
610 # Validates the issue against additional workflow requirements
610 # Validates the issue against additional workflow requirements
611 def validate_required_fields
611 def validate_required_fields
612 user = new_record? ? author : current_journal.try(:user)
612 user = new_record? ? author : current_journal.try(:user)
613
613
614 required_attribute_names(user).each do |attribute|
614 required_attribute_names(user).each do |attribute|
615 if attribute =~ /^\d+$/
615 if attribute =~ /^\d+$/
616 attribute = attribute.to_i
616 attribute = attribute.to_i
617 v = custom_field_values.detect {|v| v.custom_field_id == attribute }
617 v = custom_field_values.detect {|v| v.custom_field_id == attribute }
618 if v && v.value.blank?
618 if v && v.value.blank?
619 errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank')
619 errors.add :base, v.custom_field.name + ' ' + l('activerecord.errors.messages.blank')
620 end
620 end
621 else
621 else
622 if respond_to?(attribute) && send(attribute).blank?
622 if respond_to?(attribute) && send(attribute).blank?
623 errors.add attribute, :blank
623 errors.add attribute, :blank
624 end
624 end
625 end
625 end
626 end
626 end
627 end
627 end
628
628
629 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
629 # Set the done_ratio using the status if that setting is set. This will keep the done_ratios
630 # even if the user turns off the setting later
630 # even if the user turns off the setting later
631 def update_done_ratio_from_issue_status
631 def update_done_ratio_from_issue_status
632 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
632 if Issue.use_status_for_done_ratio? && status && status.default_done_ratio
633 self.done_ratio = status.default_done_ratio
633 self.done_ratio = status.default_done_ratio
634 end
634 end
635 end
635 end
636
636
637 def init_journal(user, notes = "")
637 def init_journal(user, notes = "")
638 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
638 @current_journal ||= Journal.new(:journalized => self, :user => user, :notes => notes)
639 if new_record?
639 if new_record?
640 @current_journal.notify = false
640 @current_journal.notify = false
641 else
641 else
642 @attributes_before_change = attributes.dup
642 @attributes_before_change = attributes.dup
643 @custom_values_before_change = {}
643 @custom_values_before_change = {}
644 self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
644 self.custom_field_values.each {|c| @custom_values_before_change.store c.custom_field_id, c.value }
645 end
645 end
646 @current_journal
646 @current_journal
647 end
647 end
648
648
649 # Returns the id of the last journal or nil
649 # Returns the id of the last journal or nil
650 def last_journal_id
650 def last_journal_id
651 if new_record?
651 if new_record?
652 nil
652 nil
653 else
653 else
654 journals.maximum(:id)
654 journals.maximum(:id)
655 end
655 end
656 end
656 end
657
657
658 # Returns a scope for journals that have an id greater than journal_id
658 # Returns a scope for journals that have an id greater than journal_id
659 def journals_after(journal_id)
659 def journals_after(journal_id)
660 scope = journals.reorder("#{Journal.table_name}.id ASC")
660 scope = journals.reorder("#{Journal.table_name}.id ASC")
661 if journal_id.present?
661 if journal_id.present?
662 scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i)
662 scope = scope.where("#{Journal.table_name}.id > ?", journal_id.to_i)
663 end
663 end
664 scope
664 scope
665 end
665 end
666
666
667 # Returns the initial status of the issue
667 # Returns the initial status of the issue
668 # Returns nil for a new issue
668 # Returns nil for a new issue
669 def status_was
669 def status_was
670 if status_id_was && status_id_was.to_i > 0
670 if status_id_was && status_id_was.to_i > 0
671 @status_was ||= IssueStatus.find_by_id(status_id_was)
671 @status_was ||= IssueStatus.find_by_id(status_id_was)
672 end
672 end
673 end
673 end
674
674
675 # Return true if the issue is closed, otherwise false
675 # Return true if the issue is closed, otherwise false
676 def closed?
676 def closed?
677 self.status.is_closed?
677 self.status.is_closed?
678 end
678 end
679
679
680 # Return true if the issue is being reopened
680 # Return true if the issue is being reopened
681 def reopened?
681 def reopened?
682 if !new_record? && status_id_changed?
682 if !new_record? && status_id_changed?
683 status_was = IssueStatus.find_by_id(status_id_was)
683 status_was = IssueStatus.find_by_id(status_id_was)
684 status_new = IssueStatus.find_by_id(status_id)
684 status_new = IssueStatus.find_by_id(status_id)
685 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
685 if status_was && status_new && status_was.is_closed? && !status_new.is_closed?
686 return true
686 return true
687 end
687 end
688 end
688 end
689 false
689 false
690 end
690 end
691
691
692 # Return true if the issue is being closed
692 # Return true if the issue is being closed
693 def closing?
693 def closing?
694 if !new_record? && status_id_changed?
694 if !new_record? && status_id_changed?
695 if status_was && status && !status_was.is_closed? && status.is_closed?
695 if status_was && status && !status_was.is_closed? && status.is_closed?
696 return true
696 return true
697 end
697 end
698 end
698 end
699 false
699 false
700 end
700 end
701
701
702 # Returns true if the issue is overdue
702 # Returns true if the issue is overdue
703 def overdue?
703 def overdue?
704 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
704 !due_date.nil? && (due_date < Date.today) && !status.is_closed?
705 end
705 end
706
706
707 # Is the amount of work done less than it should for the due date
707 # Is the amount of work done less than it should for the due date
708 def behind_schedule?
708 def behind_schedule?
709 return false if start_date.nil? || due_date.nil?
709 return false if start_date.nil? || due_date.nil?
710 done_date = start_date + ((due_date - start_date + 1) * done_ratio / 100).floor
710 done_date = start_date + ((due_date - start_date + 1) * done_ratio / 100).floor
711 return done_date <= Date.today
711 return done_date <= Date.today
712 end
712 end
713
713
714 # Does this issue have children?
714 # Does this issue have children?
715 def children?
715 def children?
716 !leaf?
716 !leaf?
717 end
717 end
718
718
719 # Users the issue can be assigned to
719 # Users the issue can be assigned to
720 def assignable_users
720 def assignable_users
721 users = project.assignable_users
721 users = project.assignable_users
722 users << author if author
722 users << author if author
723 users << assigned_to if assigned_to
723 users << assigned_to if assigned_to
724 users.uniq.sort
724 users.uniq.sort
725 end
725 end
726
726
727 # Versions that the issue can be assigned to
727 # Versions that the issue can be assigned to
728 def assignable_versions
728 def assignable_versions
729 return @assignable_versions if @assignable_versions
729 return @assignable_versions if @assignable_versions
730
730
731 versions = project.shared_versions.open.all
731 versions = project.shared_versions.open.all
732 if fixed_version
732 if fixed_version
733 if fixed_version_id_changed?
733 if fixed_version_id_changed?
734 # nothing to do
734 # nothing to do
735 elsif project_id_changed?
735 elsif project_id_changed?
736 if project.shared_versions.include?(fixed_version)
736 if project.shared_versions.include?(fixed_version)
737 versions << fixed_version
737 versions << fixed_version
738 end
738 end
739 else
739 else
740 versions << fixed_version
740 versions << fixed_version
741 end
741 end
742 end
742 end
743 @assignable_versions = versions.uniq.sort
743 @assignable_versions = versions.uniq.sort
744 end
744 end
745
745
746 # Returns true if this issue is blocked by another issue that is still open
746 # Returns true if this issue is blocked by another issue that is still open
747 def blocked?
747 def blocked?
748 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
748 !relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
749 end
749 end
750
750
751 # Returns an array of statuses that user is able to apply
751 # Returns an array of statuses that user is able to apply
752 def new_statuses_allowed_to(user=User.current, include_default=false)
752 def new_statuses_allowed_to(user=User.current, include_default=false)
753 if new_record? && @copied_from
753 if new_record? && @copied_from
754 [IssueStatus.default, @copied_from.status].compact.uniq.sort
754 [IssueStatus.default, @copied_from.status].compact.uniq.sort
755 else
755 else
756 initial_status = nil
756 initial_status = nil
757 if new_record?
757 if new_record?
758 initial_status = IssueStatus.default
758 initial_status = IssueStatus.default
759 elsif status_id_was
759 elsif status_id_was
760 initial_status = IssueStatus.find_by_id(status_id_was)
760 initial_status = IssueStatus.find_by_id(status_id_was)
761 end
761 end
762 initial_status ||= status
762 initial_status ||= status
763
763
764 initial_assigned_to_id = assigned_to_id_changed? ? assigned_to_id_was : assigned_to_id
764 initial_assigned_to_id = assigned_to_id_changed? ? assigned_to_id_was : assigned_to_id
765 assignee_transitions_allowed = initial_assigned_to_id.present? &&
765 assignee_transitions_allowed = initial_assigned_to_id.present? &&
766 (user.id == initial_assigned_to_id || user.group_ids.include?(initial_assigned_to_id))
766 (user.id == initial_assigned_to_id || user.group_ids.include?(initial_assigned_to_id))
767
767
768 statuses = initial_status.find_new_statuses_allowed_to(
768 statuses = initial_status.find_new_statuses_allowed_to(
769 user.admin ? Role.all : user.roles_for_project(project),
769 user.admin ? Role.all : user.roles_for_project(project),
770 tracker,
770 tracker,
771 author == user,
771 author == user,
772 assignee_transitions_allowed
772 assignee_transitions_allowed
773 )
773 )
774 statuses << initial_status unless statuses.empty?
774 statuses << initial_status unless statuses.empty?
775 statuses << IssueStatus.default if include_default
775 statuses << IssueStatus.default if include_default
776 statuses = statuses.compact.uniq.sort
776 statuses = statuses.compact.uniq.sort
777 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
777 blocked? ? statuses.reject {|s| s.is_closed?} : statuses
778 end
778 end
779 end
779 end
780
780
781 # Returns the previous assignee if changed
781 # Returns the previous assignee if changed
782 def assigned_to_was
782 def assigned_to_was
783 # assigned_to_id_was is reset before after_save callbacks
783 # assigned_to_id_was is reset before after_save callbacks
784 user_id = @previous_assigned_to_id || assigned_to_id_was
784 user_id = @previous_assigned_to_id || assigned_to_id_was
785 if user_id && user_id != assigned_to_id
785 if user_id && user_id != assigned_to_id
786 @assigned_to_was ||= User.find_by_id(user_id)
786 @assigned_to_was ||= User.find_by_id(user_id)
787 end
787 end
788 end
788 end
789
789
790 # Returns the users that should be notified
790 # Returns the users that should be notified
791 def notified_users
791 def notified_users
792 notified = []
792 notified = []
793 # Author and assignee are always notified unless they have been
793 # Author and assignee are always notified unless they have been
794 # locked or don't want to be notified
794 # locked or don't want to be notified
795 notified << author if author
795 notified << author if author
796 if assigned_to
796 if assigned_to
797 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
797 notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
798 end
798 end
799 if assigned_to_was
799 if assigned_to_was
800 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
800 notified += (assigned_to_was.is_a?(Group) ? assigned_to_was.users : [assigned_to_was])
801 end
801 end
802 notified = notified.select {|u| u.active? && u.notify_about?(self)}
802 notified = notified.select {|u| u.active? && u.notify_about?(self)}
803
803
804 notified += project.notified_users
804 notified += project.notified_users
805 notified.uniq!
805 notified.uniq!
806 # Remove users that can not view the issue
806 # Remove users that can not view the issue
807 notified.reject! {|user| !visible?(user)}
807 notified.reject! {|user| !visible?(user)}
808 notified
808 notified
809 end
809 end
810
810
811 # Returns the email addresses that should be notified
811 # Returns the email addresses that should be notified
812 def recipients
812 def recipients
813 notified_users.collect(&:mail)
813 notified_users.collect(&:mail)
814 end
814 end
815
815
816 def each_notification(users, &block)
816 def each_notification(users, &block)
817 if users.any?
817 if users.any?
818 if custom_field_values.detect {|value| !value.custom_field.visible?}
818 if custom_field_values.detect {|value| !value.custom_field.visible?}
819 users_by_custom_field_visibility = users.group_by do |user|
819 users_by_custom_field_visibility = users.group_by do |user|
820 visible_custom_field_values(user).map(&:custom_field_id).sort
820 visible_custom_field_values(user).map(&:custom_field_id).sort
821 end
821 end
822 users_by_custom_field_visibility.values.each do |users|
822 users_by_custom_field_visibility.values.each do |users|
823 yield(users)
823 yield(users)
824 end
824 end
825 else
825 else
826 yield(users)
826 yield(users)
827 end
827 end
828 end
828 end
829 end
829 end
830
830
831 # Returns the number of hours spent on this issue
831 # Returns the number of hours spent on this issue
832 def spent_hours
832 def spent_hours
833 @spent_hours ||= time_entries.sum(:hours) || 0
833 @spent_hours ||= time_entries.sum(:hours) || 0
834 end
834 end
835
835
836 # Returns the total number of hours spent on this issue and its descendants
836 # Returns the total number of hours spent on this issue and its descendants
837 #
837 #
838 # Example:
838 # Example:
839 # spent_hours => 0.0
839 # spent_hours => 0.0
840 # spent_hours => 50.2
840 # spent_hours => 50.2
841 def total_spent_hours
841 def total_spent_hours
842 @total_spent_hours ||=
842 @total_spent_hours ||=
843 self_and_descendants.
843 self_and_descendants.
844 joins("LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").
844 joins("LEFT JOIN #{TimeEntry.table_name} ON #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id").
845 sum("#{TimeEntry.table_name}.hours").to_f || 0.0
845 sum("#{TimeEntry.table_name}.hours").to_f || 0.0
846 end
846 end
847
847
848 def relations
848 def relations
849 @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
849 @relations ||= IssueRelation::Relations.new(self, (relations_from + relations_to).sort)
850 end
850 end
851
851
852 # Preloads relations for a collection of issues
852 # Preloads relations for a collection of issues
853 def self.load_relations(issues)
853 def self.load_relations(issues)
854 if issues.any?
854 if issues.any?
855 relations = IssueRelation.where("issue_from_id IN (:ids) OR issue_to_id IN (:ids)", :ids => issues.map(&:id)).all
855 relations = IssueRelation.where("issue_from_id IN (:ids) OR issue_to_id IN (:ids)", :ids => issues.map(&:id)).all
856 issues.each do |issue|
856 issues.each do |issue|
857 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
857 issue.instance_variable_set "@relations", relations.select {|r| r.issue_from_id == issue.id || r.issue_to_id == issue.id}
858 end
858 end
859 end
859 end
860 end
860 end
861
861
862 # Preloads visible spent time for a collection of issues
862 # Preloads visible spent time for a collection of issues
863 def self.load_visible_spent_hours(issues, user=User.current)
863 def self.load_visible_spent_hours(issues, user=User.current)
864 if issues.any?
864 if issues.any?
865 hours_by_issue_id = TimeEntry.visible(user).group(:issue_id).sum(:hours)
865 hours_by_issue_id = TimeEntry.visible(user).group(:issue_id).sum(:hours)
866 issues.each do |issue|
866 issues.each do |issue|
867 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
867 issue.instance_variable_set "@spent_hours", (hours_by_issue_id[issue.id] || 0)
868 end
868 end
869 end
869 end
870 end
870 end
871
871
872 # Preloads visible relations for a collection of issues
872 # Preloads visible relations for a collection of issues
873 def self.load_visible_relations(issues, user=User.current)
873 def self.load_visible_relations(issues, user=User.current)
874 if issues.any?
874 if issues.any?
875 issue_ids = issues.map(&:id)
875 issue_ids = issues.map(&:id)
876 # Relations with issue_from in given issues and visible issue_to
876 # Relations with issue_from in given issues and visible issue_to
877 relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all
877 relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all
878 # Relations with issue_to in given issues and visible issue_from
878 # Relations with issue_to in given issues and visible issue_from
879 relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all
879 relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all
880
880
881 issues.each do |issue|
881 issues.each do |issue|
882 relations =
882 relations =
883 relations_from.select {|relation| relation.issue_from_id == issue.id} +
883 relations_from.select {|relation| relation.issue_from_id == issue.id} +
884 relations_to.select {|relation| relation.issue_to_id == issue.id}
884 relations_to.select {|relation| relation.issue_to_id == issue.id}
885
885
886 issue.instance_variable_set "@relations", IssueRelation::Relations.new(issue, relations.sort)
886 issue.instance_variable_set "@relations", IssueRelation::Relations.new(issue, relations.sort)
887 end
887 end
888 end
888 end
889 end
889 end
890
890
891 # Finds an issue relation given its id.
891 # Finds an issue relation given its id.
892 def find_relation(relation_id)
892 def find_relation(relation_id)
893 IssueRelation.where("issue_to_id = ? OR issue_from_id = ?", id, id).find(relation_id)
893 IssueRelation.where("issue_to_id = ? OR issue_from_id = ?", id, id).find(relation_id)
894 end
894 end
895
895
896 # Returns all the other issues that depend on the issue
896 # Returns all the other issues that depend on the issue
897 # The algorithm is a modified breadth first search (bfs)
897 # The algorithm is a modified breadth first search (bfs)
898 def all_dependent_issues(except=[])
898 def all_dependent_issues(except=[])
899 # The found dependencies
899 # The found dependencies
900 dependencies = []
900 dependencies = []
901
901
902 # The visited flag for every node (issue) used by the breadth first search
902 # The visited flag for every node (issue) used by the breadth first search
903 eNOT_DISCOVERED = 0 # The issue is "new" to the algorithm, it has not seen it before.
903 eNOT_DISCOVERED = 0 # The issue is "new" to the algorithm, it has not seen it before.
904
904
905 ePROCESS_ALL = 1 # The issue is added to the queue. Process both children and relations of
905 ePROCESS_ALL = 1 # The issue is added to the queue. Process both children and relations of
906 # the issue when it is processed.
906 # the issue when it is processed.
907
907
908 ePROCESS_RELATIONS_ONLY = 2 # The issue was added to the queue and will be output as dependent issue,
908 ePROCESS_RELATIONS_ONLY = 2 # The issue was added to the queue and will be output as dependent issue,
909 # but its children will not be added to the queue when it is processed.
909 # but its children will not be added to the queue when it is processed.
910
910
911 eRELATIONS_PROCESSED = 3 # The related issues, the parent issue and the issue itself have been added to
911 eRELATIONS_PROCESSED = 3 # The related issues, the parent issue and the issue itself have been added to
912 # the queue, but its children have not been added.
912 # the queue, but its children have not been added.
913
913
914 ePROCESS_CHILDREN_ONLY = 4 # The relations and the parent of the issue have been added to the queue, but
914 ePROCESS_CHILDREN_ONLY = 4 # The relations and the parent of the issue have been added to the queue, but
915 # the children still need to be processed.
915 # the children still need to be processed.
916
916
917 eALL_PROCESSED = 5 # The issue and all its children, its parent and its related issues have been
917 eALL_PROCESSED = 5 # The issue and all its children, its parent and its related issues have been
918 # added as dependent issues. It needs no further processing.
918 # added as dependent issues. It needs no further processing.
919
919
920 issue_status = Hash.new(eNOT_DISCOVERED)
920 issue_status = Hash.new(eNOT_DISCOVERED)
921
921
922 # The queue
922 # The queue
923 queue = []
923 queue = []
924
924
925 # Initialize the bfs, add start node (self) to the queue
925 # Initialize the bfs, add start node (self) to the queue
926 queue << self
926 queue << self
927 issue_status[self] = ePROCESS_ALL
927 issue_status[self] = ePROCESS_ALL
928
928
929 while (!queue.empty?) do
929 while (!queue.empty?) do
930 current_issue = queue.shift
930 current_issue = queue.shift
931 current_issue_status = issue_status[current_issue]
931 current_issue_status = issue_status[current_issue]
932 dependencies << current_issue
932 dependencies << current_issue
933
933
934 # Add parent to queue, if not already in it.
934 # Add parent to queue, if not already in it.
935 parent = current_issue.parent
935 parent = current_issue.parent
936 parent_status = issue_status[parent]
936 parent_status = issue_status[parent]
937
937
938 if parent && (parent_status == eNOT_DISCOVERED) && !except.include?(parent)
938 if parent && (parent_status == eNOT_DISCOVERED) && !except.include?(parent)
939 queue << parent
939 queue << parent
940 issue_status[parent] = ePROCESS_RELATIONS_ONLY
940 issue_status[parent] = ePROCESS_RELATIONS_ONLY
941 end
941 end
942
942
943 # Add children to queue, but only if they are not already in it and
943 # Add children to queue, but only if they are not already in it and
944 # the children of the current node need to be processed.
944 # the children of the current node need to be processed.
945 if (current_issue_status == ePROCESS_CHILDREN_ONLY || current_issue_status == ePROCESS_ALL)
945 if (current_issue_status == ePROCESS_CHILDREN_ONLY || current_issue_status == ePROCESS_ALL)
946 current_issue.children.each do |child|
946 current_issue.children.each do |child|
947 next if except.include?(child)
947 next if except.include?(child)
948
948
949 if (issue_status[child] == eNOT_DISCOVERED)
949 if (issue_status[child] == eNOT_DISCOVERED)
950 queue << child
950 queue << child
951 issue_status[child] = ePROCESS_ALL
951 issue_status[child] = ePROCESS_ALL
952 elsif (issue_status[child] == eRELATIONS_PROCESSED)
952 elsif (issue_status[child] == eRELATIONS_PROCESSED)
953 queue << child
953 queue << child
954 issue_status[child] = ePROCESS_CHILDREN_ONLY
954 issue_status[child] = ePROCESS_CHILDREN_ONLY
955 elsif (issue_status[child] == ePROCESS_RELATIONS_ONLY)
955 elsif (issue_status[child] == ePROCESS_RELATIONS_ONLY)
956 queue << child
956 queue << child
957 issue_status[child] = ePROCESS_ALL
957 issue_status[child] = ePROCESS_ALL
958 end
958 end
959 end
959 end
960 end
960 end
961
961
962 # Add related issues to the queue, if they are not already in it.
962 # Add related issues to the queue, if they are not already in it.
963 current_issue.relations_from.map(&:issue_to).each do |related_issue|
963 current_issue.relations_from.map(&:issue_to).each do |related_issue|
964 next if except.include?(related_issue)
964 next if except.include?(related_issue)
965
965
966 if (issue_status[related_issue] == eNOT_DISCOVERED)
966 if (issue_status[related_issue] == eNOT_DISCOVERED)
967 queue << related_issue
967 queue << related_issue
968 issue_status[related_issue] = ePROCESS_ALL
968 issue_status[related_issue] = ePROCESS_ALL
969 elsif (issue_status[related_issue] == eRELATIONS_PROCESSED)
969 elsif (issue_status[related_issue] == eRELATIONS_PROCESSED)
970 queue << related_issue
970 queue << related_issue
971 issue_status[related_issue] = ePROCESS_CHILDREN_ONLY
971 issue_status[related_issue] = ePROCESS_CHILDREN_ONLY
972 elsif (issue_status[related_issue] == ePROCESS_RELATIONS_ONLY)
972 elsif (issue_status[related_issue] == ePROCESS_RELATIONS_ONLY)
973 queue << related_issue
973 queue << related_issue
974 issue_status[related_issue] = ePROCESS_ALL
974 issue_status[related_issue] = ePROCESS_ALL
975 end
975 end
976 end
976 end
977
977
978 # Set new status for current issue
978 # Set new status for current issue
979 if (current_issue_status == ePROCESS_ALL) || (current_issue_status == ePROCESS_CHILDREN_ONLY)
979 if (current_issue_status == ePROCESS_ALL) || (current_issue_status == ePROCESS_CHILDREN_ONLY)
980 issue_status[current_issue] = eALL_PROCESSED
980 issue_status[current_issue] = eALL_PROCESSED
981 elsif (current_issue_status == ePROCESS_RELATIONS_ONLY)
981 elsif (current_issue_status == ePROCESS_RELATIONS_ONLY)
982 issue_status[current_issue] = eRELATIONS_PROCESSED
982 issue_status[current_issue] = eRELATIONS_PROCESSED
983 end
983 end
984 end # while
984 end # while
985
985
986 # Remove the issues from the "except" parameter from the result array
986 # Remove the issues from the "except" parameter from the result array
987 dependencies -= except
987 dependencies -= except
988 dependencies.delete(self)
988 dependencies.delete(self)
989
989
990 dependencies
990 dependencies
991 end
991 end
992
992
993 # Returns an array of issues that duplicate this one
993 # Returns an array of issues that duplicate this one
994 def duplicates
994 def duplicates
995 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
995 relations_to.select {|r| r.relation_type == IssueRelation::TYPE_DUPLICATES}.collect {|r| r.issue_from}
996 end
996 end
997
997
998 # Returns the due date or the target due date if any
998 # Returns the due date or the target due date if any
999 # Used on gantt chart
999 # Used on gantt chart
1000 def due_before
1000 def due_before
1001 due_date || (fixed_version ? fixed_version.effective_date : nil)
1001 due_date || (fixed_version ? fixed_version.effective_date : nil)
1002 end
1002 end
1003
1003
1004 # Returns the time scheduled for this issue.
1004 # Returns the time scheduled for this issue.
1005 #
1005 #
1006 # Example:
1006 # Example:
1007 # Start Date: 2/26/09, End Date: 3/04/09
1007 # Start Date: 2/26/09, End Date: 3/04/09
1008 # duration => 6
1008 # duration => 6
1009 def duration
1009 def duration
1010 (start_date && due_date) ? due_date - start_date : 0
1010 (start_date && due_date) ? due_date - start_date : 0
1011 end
1011 end
1012
1012
1013 # Returns the duration in working days
1013 # Returns the duration in working days
1014 def working_duration
1014 def working_duration
1015 (start_date && due_date) ? working_days(start_date, due_date) : 0
1015 (start_date && due_date) ? working_days(start_date, due_date) : 0
1016 end
1016 end
1017
1017
1018 def soonest_start(reload=false)
1018 def soonest_start(reload=false)
1019 @soonest_start = nil if reload
1019 @soonest_start = nil if reload
1020 @soonest_start ||= (
1020 @soonest_start ||= (
1021 relations_to(reload).collect{|relation| relation.successor_soonest_start} +
1021 relations_to(reload).collect{|relation| relation.successor_soonest_start} +
1022 [(@parent_issue || parent).try(:soonest_start)]
1022 [(@parent_issue || parent).try(:soonest_start)]
1023 ).compact.max
1023 ).compact.max
1024 end
1024 end
1025
1025
1026 # Sets start_date on the given date or the next working day
1026 # Sets start_date on the given date or the next working day
1027 # and changes due_date to keep the same working duration.
1027 # and changes due_date to keep the same working duration.
1028 def reschedule_on(date)
1028 def reschedule_on(date)
1029 wd = working_duration
1029 wd = working_duration
1030 date = next_working_date(date)
1030 date = next_working_date(date)
1031 self.start_date = date
1031 self.start_date = date
1032 self.due_date = add_working_days(date, wd)
1032 self.due_date = add_working_days(date, wd)
1033 end
1033 end
1034
1034
1035 # Reschedules the issue on the given date or the next working day and saves the record.
1035 # Reschedules the issue on the given date or the next working day and saves the record.
1036 # If the issue is a parent task, this is done by rescheduling its subtasks.
1036 # If the issue is a parent task, this is done by rescheduling its subtasks.
1037 def reschedule_on!(date)
1037 def reschedule_on!(date)
1038 return if date.nil?
1038 return if date.nil?
1039 if leaf?
1039 if leaf?
1040 if start_date.nil? || start_date != date
1040 if start_date.nil? || start_date != date
1041 if start_date && start_date > date
1041 if start_date && start_date > date
1042 # Issue can not be moved earlier than its soonest start date
1042 # Issue can not be moved earlier than its soonest start date
1043 date = [soonest_start(true), date].compact.max
1043 date = [soonest_start(true), date].compact.max
1044 end
1044 end
1045 reschedule_on(date)
1045 reschedule_on(date)
1046 begin
1046 begin
1047 save
1047 save
1048 rescue ActiveRecord::StaleObjectError
1048 rescue ActiveRecord::StaleObjectError
1049 reload
1049 reload
1050 reschedule_on(date)
1050 reschedule_on(date)
1051 save
1051 save
1052 end
1052 end
1053 end
1053 end
1054 else
1054 else
1055 leaves.each do |leaf|
1055 leaves.each do |leaf|
1056 if leaf.start_date
1056 if leaf.start_date
1057 # Only move subtask if it starts at the same date as the parent
1057 # Only move subtask if it starts at the same date as the parent
1058 # or if it starts before the given date
1058 # or if it starts before the given date
1059 if start_date == leaf.start_date || date > leaf.start_date
1059 if start_date == leaf.start_date || date > leaf.start_date
1060 leaf.reschedule_on!(date)
1060 leaf.reschedule_on!(date)
1061 end
1061 end
1062 else
1062 else
1063 leaf.reschedule_on!(date)
1063 leaf.reschedule_on!(date)
1064 end
1064 end
1065 end
1065 end
1066 end
1066 end
1067 end
1067 end
1068
1068
1069 def <=>(issue)
1069 def <=>(issue)
1070 if issue.nil?
1070 if issue.nil?
1071 -1
1071 -1
1072 elsif root_id != issue.root_id
1072 elsif root_id != issue.root_id
1073 (root_id || 0) <=> (issue.root_id || 0)
1073 (root_id || 0) <=> (issue.root_id || 0)
1074 else
1074 else
1075 (lft || 0) <=> (issue.lft || 0)
1075 (lft || 0) <=> (issue.lft || 0)
1076 end
1076 end
1077 end
1077 end
1078
1078
1079 def to_s
1079 def to_s
1080 "#{tracker} ##{id}: #{subject}"
1080 "#{tracker} ##{id}: #{subject}"
1081 end
1081 end
1082
1082
1083 # Returns a string of css classes that apply to the issue
1083 # Returns a string of css classes that apply to the issue
1084 def css_classes(user=User.current)
1084 def css_classes(user=User.current)
1085 s = "issue tracker-#{tracker_id} status-#{status_id} #{priority.try(:css_classes)}"
1085 s = "issue tracker-#{tracker_id} status-#{status_id} #{priority.try(:css_classes)}"
1086 s << ' closed' if closed?
1086 s << ' closed' if closed?
1087 s << ' overdue' if overdue?
1087 s << ' overdue' if overdue?
1088 s << ' child' if child?
1088 s << ' child' if child?
1089 s << ' parent' unless leaf?
1089 s << ' parent' unless leaf?
1090 s << ' private' if is_private?
1090 s << ' private' if is_private?
1091 if user.logged?
1091 if user.logged?
1092 s << ' created-by-me' if author_id == user.id
1092 s << ' created-by-me' if author_id == user.id
1093 s << ' assigned-to-me' if assigned_to_id == user.id
1093 s << ' assigned-to-me' if assigned_to_id == user.id
1094 s << ' assigned-to-my-group' if user.groups.any? {|g| g.id = assigned_to_id}
1094 s << ' assigned-to-my-group' if user.groups.any? {|g| g.id == assigned_to_id}
1095 end
1095 end
1096 s
1096 s
1097 end
1097 end
1098
1098
1099 # Unassigns issues from +version+ if it's no longer shared with issue's project
1099 # Unassigns issues from +version+ if it's no longer shared with issue's project
1100 def self.update_versions_from_sharing_change(version)
1100 def self.update_versions_from_sharing_change(version)
1101 # Update issues assigned to the version
1101 # Update issues assigned to the version
1102 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
1102 update_versions(["#{Issue.table_name}.fixed_version_id = ?", version.id])
1103 end
1103 end
1104
1104
1105 # Unassigns issues from versions that are no longer shared
1105 # Unassigns issues from versions that are no longer shared
1106 # after +project+ was moved
1106 # after +project+ was moved
1107 def self.update_versions_from_hierarchy_change(project)
1107 def self.update_versions_from_hierarchy_change(project)
1108 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
1108 moved_project_ids = project.self_and_descendants.reload.collect(&:id)
1109 # Update issues of the moved projects and issues assigned to a version of a moved project
1109 # Update issues of the moved projects and issues assigned to a version of a moved project
1110 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
1110 Issue.update_versions(["#{Version.table_name}.project_id IN (?) OR #{Issue.table_name}.project_id IN (?)", moved_project_ids, moved_project_ids])
1111 end
1111 end
1112
1112
1113 def parent_issue_id=(arg)
1113 def parent_issue_id=(arg)
1114 s = arg.to_s.strip.presence
1114 s = arg.to_s.strip.presence
1115 if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
1115 if s && (m = s.match(%r{\A#?(\d+)\z})) && (@parent_issue = Issue.find_by_id(m[1]))
1116 @parent_issue.id
1116 @parent_issue.id
1117 @invalid_parent_issue_id = nil
1117 @invalid_parent_issue_id = nil
1118 elsif s.blank?
1118 elsif s.blank?
1119 @parent_issue = nil
1119 @parent_issue = nil
1120 @invalid_parent_issue_id = nil
1120 @invalid_parent_issue_id = nil
1121 else
1121 else
1122 @parent_issue = nil
1122 @parent_issue = nil
1123 @invalid_parent_issue_id = arg
1123 @invalid_parent_issue_id = arg
1124 end
1124 end
1125 end
1125 end
1126
1126
1127 def parent_issue_id
1127 def parent_issue_id
1128 if @invalid_parent_issue_id
1128 if @invalid_parent_issue_id
1129 @invalid_parent_issue_id
1129 @invalid_parent_issue_id
1130 elsif instance_variable_defined? :@parent_issue
1130 elsif instance_variable_defined? :@parent_issue
1131 @parent_issue.nil? ? nil : @parent_issue.id
1131 @parent_issue.nil? ? nil : @parent_issue.id
1132 else
1132 else
1133 parent_id
1133 parent_id
1134 end
1134 end
1135 end
1135 end
1136
1136
1137 # Returns true if issue's project is a valid
1137 # Returns true if issue's project is a valid
1138 # parent issue project
1138 # parent issue project
1139 def valid_parent_project?(issue=parent)
1139 def valid_parent_project?(issue=parent)
1140 return true if issue.nil? || issue.project_id == project_id
1140 return true if issue.nil? || issue.project_id == project_id
1141
1141
1142 case Setting.cross_project_subtasks
1142 case Setting.cross_project_subtasks
1143 when 'system'
1143 when 'system'
1144 true
1144 true
1145 when 'tree'
1145 when 'tree'
1146 issue.project.root == project.root
1146 issue.project.root == project.root
1147 when 'hierarchy'
1147 when 'hierarchy'
1148 issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
1148 issue.project.is_or_is_ancestor_of?(project) || issue.project.is_descendant_of?(project)
1149 when 'descendants'
1149 when 'descendants'
1150 issue.project.is_or_is_ancestor_of?(project)
1150 issue.project.is_or_is_ancestor_of?(project)
1151 else
1151 else
1152 false
1152 false
1153 end
1153 end
1154 end
1154 end
1155
1155
1156 # Extracted from the ReportsController.
1156 # Extracted from the ReportsController.
1157 def self.by_tracker(project)
1157 def self.by_tracker(project)
1158 count_and_group_by(:project => project,
1158 count_and_group_by(:project => project,
1159 :field => 'tracker_id',
1159 :field => 'tracker_id',
1160 :joins => Tracker.table_name)
1160 :joins => Tracker.table_name)
1161 end
1161 end
1162
1162
1163 def self.by_version(project)
1163 def self.by_version(project)
1164 count_and_group_by(:project => project,
1164 count_and_group_by(:project => project,
1165 :field => 'fixed_version_id',
1165 :field => 'fixed_version_id',
1166 :joins => Version.table_name)
1166 :joins => Version.table_name)
1167 end
1167 end
1168
1168
1169 def self.by_priority(project)
1169 def self.by_priority(project)
1170 count_and_group_by(:project => project,
1170 count_and_group_by(:project => project,
1171 :field => 'priority_id',
1171 :field => 'priority_id',
1172 :joins => IssuePriority.table_name)
1172 :joins => IssuePriority.table_name)
1173 end
1173 end
1174
1174
1175 def self.by_category(project)
1175 def self.by_category(project)
1176 count_and_group_by(:project => project,
1176 count_and_group_by(:project => project,
1177 :field => 'category_id',
1177 :field => 'category_id',
1178 :joins => IssueCategory.table_name)
1178 :joins => IssueCategory.table_name)
1179 end
1179 end
1180
1180
1181 def self.by_assigned_to(project)
1181 def self.by_assigned_to(project)
1182 count_and_group_by(:project => project,
1182 count_and_group_by(:project => project,
1183 :field => 'assigned_to_id',
1183 :field => 'assigned_to_id',
1184 :joins => User.table_name)
1184 :joins => User.table_name)
1185 end
1185 end
1186
1186
1187 def self.by_author(project)
1187 def self.by_author(project)
1188 count_and_group_by(:project => project,
1188 count_and_group_by(:project => project,
1189 :field => 'author_id',
1189 :field => 'author_id',
1190 :joins => User.table_name)
1190 :joins => User.table_name)
1191 end
1191 end
1192
1192
1193 def self.by_subproject(project)
1193 def self.by_subproject(project)
1194 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1194 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1195 s.is_closed as closed,
1195 s.is_closed as closed,
1196 #{Issue.table_name}.project_id as project_id,
1196 #{Issue.table_name}.project_id as project_id,
1197 count(#{Issue.table_name}.id) as total
1197 count(#{Issue.table_name}.id) as total
1198 from
1198 from
1199 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
1199 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s
1200 where
1200 where
1201 #{Issue.table_name}.status_id=s.id
1201 #{Issue.table_name}.status_id=s.id
1202 and #{Issue.table_name}.project_id = #{Project.table_name}.id
1202 and #{Issue.table_name}.project_id = #{Project.table_name}.id
1203 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
1203 and #{visible_condition(User.current, :project => project, :with_subprojects => true)}
1204 and #{Issue.table_name}.project_id <> #{project.id}
1204 and #{Issue.table_name}.project_id <> #{project.id}
1205 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
1205 group by s.id, s.is_closed, #{Issue.table_name}.project_id") if project.descendants.active.any?
1206 end
1206 end
1207 # End ReportsController extraction
1207 # End ReportsController extraction
1208
1208
1209 # Returns a scope of projects that user can assign the issue to
1209 # Returns a scope of projects that user can assign the issue to
1210 def allowed_target_projects(user=User.current)
1210 def allowed_target_projects(user=User.current)
1211 if new_record?
1211 if new_record?
1212 Project.where(Project.allowed_to_condition(user, :add_issues))
1212 Project.where(Project.allowed_to_condition(user, :add_issues))
1213 else
1213 else
1214 self.class.allowed_target_projects_on_move(user)
1214 self.class.allowed_target_projects_on_move(user)
1215 end
1215 end
1216 end
1216 end
1217
1217
1218 # Returns a scope of projects that user can move issues to
1218 # Returns a scope of projects that user can move issues to
1219 def self.allowed_target_projects_on_move(user=User.current)
1219 def self.allowed_target_projects_on_move(user=User.current)
1220 Project.where(Project.allowed_to_condition(user, :move_issues))
1220 Project.where(Project.allowed_to_condition(user, :move_issues))
1221 end
1221 end
1222
1222
1223 private
1223 private
1224
1224
1225 def after_project_change
1225 def after_project_change
1226 # Update project_id on related time entries
1226 # Update project_id on related time entries
1227 TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id])
1227 TimeEntry.where({:issue_id => id}).update_all(["project_id = ?", project_id])
1228
1228
1229 # Delete issue relations
1229 # Delete issue relations
1230 unless Setting.cross_project_issue_relations?
1230 unless Setting.cross_project_issue_relations?
1231 relations_from.clear
1231 relations_from.clear
1232 relations_to.clear
1232 relations_to.clear
1233 end
1233 end
1234
1234
1235 # Move subtasks that were in the same project
1235 # Move subtasks that were in the same project
1236 children.each do |child|
1236 children.each do |child|
1237 next unless child.project_id == project_id_was
1237 next unless child.project_id == project_id_was
1238 # Change project and keep project
1238 # Change project and keep project
1239 child.send :project=, project, true
1239 child.send :project=, project, true
1240 unless child.save
1240 unless child.save
1241 raise ActiveRecord::Rollback
1241 raise ActiveRecord::Rollback
1242 end
1242 end
1243 end
1243 end
1244 end
1244 end
1245
1245
1246 # Callback for after the creation of an issue by copy
1246 # Callback for after the creation of an issue by copy
1247 # * adds a "copied to" relation with the copied issue
1247 # * adds a "copied to" relation with the copied issue
1248 # * copies subtasks from the copied issue
1248 # * copies subtasks from the copied issue
1249 def after_create_from_copy
1249 def after_create_from_copy
1250 return unless copy? && !@after_create_from_copy_handled
1250 return unless copy? && !@after_create_from_copy_handled
1251
1251
1252 if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
1252 if (@copied_from.project_id == project_id || Setting.cross_project_issue_relations?) && @copy_options[:link] != false
1253 relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
1253 relation = IssueRelation.new(:issue_from => @copied_from, :issue_to => self, :relation_type => IssueRelation::TYPE_COPIED_TO)
1254 unless relation.save
1254 unless relation.save
1255 logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
1255 logger.error "Could not create relation while copying ##{@copied_from.id} to ##{id} due to validation errors: #{relation.errors.full_messages.join(', ')}" if logger
1256 end
1256 end
1257 end
1257 end
1258
1258
1259 unless @copied_from.leaf? || @copy_options[:subtasks] == false
1259 unless @copied_from.leaf? || @copy_options[:subtasks] == false
1260 copy_options = (@copy_options || {}).merge(:subtasks => false)
1260 copy_options = (@copy_options || {}).merge(:subtasks => false)
1261 copied_issue_ids = {@copied_from.id => self.id}
1261 copied_issue_ids = {@copied_from.id => self.id}
1262 @copied_from.reload.descendants.reorder("#{Issue.table_name}.lft").each do |child|
1262 @copied_from.reload.descendants.reorder("#{Issue.table_name}.lft").each do |child|
1263 # Do not copy self when copying an issue as a descendant of the copied issue
1263 # Do not copy self when copying an issue as a descendant of the copied issue
1264 next if child == self
1264 next if child == self
1265 # Do not copy subtasks of issues that were not copied
1265 # Do not copy subtasks of issues that were not copied
1266 next unless copied_issue_ids[child.parent_id]
1266 next unless copied_issue_ids[child.parent_id]
1267 # Do not copy subtasks that are not visible to avoid potential disclosure of private data
1267 # Do not copy subtasks that are not visible to avoid potential disclosure of private data
1268 unless child.visible?
1268 unless child.visible?
1269 logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger
1269 logger.error "Subtask ##{child.id} was not copied during ##{@copied_from.id} copy because it is not visible to the current user" if logger
1270 next
1270 next
1271 end
1271 end
1272 copy = Issue.new.copy_from(child, copy_options)
1272 copy = Issue.new.copy_from(child, copy_options)
1273 copy.author = author
1273 copy.author = author
1274 copy.project = project
1274 copy.project = project
1275 copy.parent_issue_id = copied_issue_ids[child.parent_id]
1275 copy.parent_issue_id = copied_issue_ids[child.parent_id]
1276 unless copy.save
1276 unless copy.save
1277 logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
1277 logger.error "Could not copy subtask ##{child.id} while copying ##{@copied_from.id} to ##{id} due to validation errors: #{copy.errors.full_messages.join(', ')}" if logger
1278 next
1278 next
1279 end
1279 end
1280 copied_issue_ids[child.id] = copy.id
1280 copied_issue_ids[child.id] = copy.id
1281 end
1281 end
1282 end
1282 end
1283 @after_create_from_copy_handled = true
1283 @after_create_from_copy_handled = true
1284 end
1284 end
1285
1285
1286 def update_nested_set_attributes
1286 def update_nested_set_attributes
1287 if root_id.nil?
1287 if root_id.nil?
1288 # issue was just created
1288 # issue was just created
1289 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
1289 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id)
1290 set_default_left_and_right
1290 set_default_left_and_right
1291 Issue.where(["id = ?", id]).
1291 Issue.where(["id = ?", id]).
1292 update_all(["root_id = ?, lft = ?, rgt = ?", root_id, lft, rgt])
1292 update_all(["root_id = ?, lft = ?, rgt = ?", root_id, lft, rgt])
1293 if @parent_issue
1293 if @parent_issue
1294 move_to_child_of(@parent_issue)
1294 move_to_child_of(@parent_issue)
1295 end
1295 end
1296 elsif parent_issue_id != parent_id
1296 elsif parent_issue_id != parent_id
1297 update_nested_set_attributes_on_parent_change
1297 update_nested_set_attributes_on_parent_change
1298 end
1298 end
1299 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
1299 remove_instance_variable(:@parent_issue) if instance_variable_defined?(:@parent_issue)
1300 end
1300 end
1301
1301
1302 # Updates the nested set for when an existing issue is moved
1302 # Updates the nested set for when an existing issue is moved
1303 def update_nested_set_attributes_on_parent_change
1303 def update_nested_set_attributes_on_parent_change
1304 former_parent_id = parent_id
1304 former_parent_id = parent_id
1305 # moving an existing issue
1305 # moving an existing issue
1306 if @parent_issue && @parent_issue.root_id == root_id
1306 if @parent_issue && @parent_issue.root_id == root_id
1307 # inside the same tree
1307 # inside the same tree
1308 move_to_child_of(@parent_issue)
1308 move_to_child_of(@parent_issue)
1309 else
1309 else
1310 # to another tree
1310 # to another tree
1311 unless root?
1311 unless root?
1312 move_to_right_of(root)
1312 move_to_right_of(root)
1313 end
1313 end
1314 old_root_id = root_id
1314 old_root_id = root_id
1315 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
1315 self.root_id = (@parent_issue.nil? ? id : @parent_issue.root_id )
1316 target_maxright = nested_set_scope.maximum(right_column_name) || 0
1316 target_maxright = nested_set_scope.maximum(right_column_name) || 0
1317 offset = target_maxright + 1 - lft
1317 offset = target_maxright + 1 - lft
1318 Issue.where(["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt]).
1318 Issue.where(["root_id = ? AND lft >= ? AND rgt <= ? ", old_root_id, lft, rgt]).
1319 update_all(["root_id = ?, lft = lft + ?, rgt = rgt + ?", root_id, offset, offset])
1319 update_all(["root_id = ?, lft = lft + ?, rgt = rgt + ?", root_id, offset, offset])
1320 self[left_column_name] = lft + offset
1320 self[left_column_name] = lft + offset
1321 self[right_column_name] = rgt + offset
1321 self[right_column_name] = rgt + offset
1322 if @parent_issue
1322 if @parent_issue
1323 move_to_child_of(@parent_issue)
1323 move_to_child_of(@parent_issue)
1324 end
1324 end
1325 end
1325 end
1326 # delete invalid relations of all descendants
1326 # delete invalid relations of all descendants
1327 self_and_descendants.each do |issue|
1327 self_and_descendants.each do |issue|
1328 issue.relations.each do |relation|
1328 issue.relations.each do |relation|
1329 relation.destroy unless relation.valid?
1329 relation.destroy unless relation.valid?
1330 end
1330 end
1331 end
1331 end
1332 # update former parent
1332 # update former parent
1333 recalculate_attributes_for(former_parent_id) if former_parent_id
1333 recalculate_attributes_for(former_parent_id) if former_parent_id
1334 end
1334 end
1335
1335
1336 def update_parent_attributes
1336 def update_parent_attributes
1337 recalculate_attributes_for(parent_id) if parent_id
1337 recalculate_attributes_for(parent_id) if parent_id
1338 end
1338 end
1339
1339
1340 def recalculate_attributes_for(issue_id)
1340 def recalculate_attributes_for(issue_id)
1341 if issue_id && p = Issue.find_by_id(issue_id)
1341 if issue_id && p = Issue.find_by_id(issue_id)
1342 # priority = highest priority of children
1342 # priority = highest priority of children
1343 if priority_position = p.children.joins(:priority).maximum("#{IssuePriority.table_name}.position")
1343 if priority_position = p.children.joins(:priority).maximum("#{IssuePriority.table_name}.position")
1344 p.priority = IssuePriority.find_by_position(priority_position)
1344 p.priority = IssuePriority.find_by_position(priority_position)
1345 end
1345 end
1346
1346
1347 # start/due dates = lowest/highest dates of children
1347 # start/due dates = lowest/highest dates of children
1348 p.start_date = p.children.minimum(:start_date)
1348 p.start_date = p.children.minimum(:start_date)
1349 p.due_date = p.children.maximum(:due_date)
1349 p.due_date = p.children.maximum(:due_date)
1350 if p.start_date && p.due_date && p.due_date < p.start_date
1350 if p.start_date && p.due_date && p.due_date < p.start_date
1351 p.start_date, p.due_date = p.due_date, p.start_date
1351 p.start_date, p.due_date = p.due_date, p.start_date
1352 end
1352 end
1353
1353
1354 # done ratio = weighted average ratio of leaves
1354 # done ratio = weighted average ratio of leaves
1355 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
1355 unless Issue.use_status_for_done_ratio? && p.status && p.status.default_done_ratio
1356 leaves_count = p.leaves.count
1356 leaves_count = p.leaves.count
1357 if leaves_count > 0
1357 if leaves_count > 0
1358 average = p.leaves.where("estimated_hours > 0").average(:estimated_hours).to_f
1358 average = p.leaves.where("estimated_hours > 0").average(:estimated_hours).to_f
1359 if average == 0
1359 if average == 0
1360 average = 1
1360 average = 1
1361 end
1361 end
1362 done = p.leaves.joins(:status).
1362 done = p.leaves.joins(:status).
1363 sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " +
1363 sum("COALESCE(CASE WHEN estimated_hours > 0 THEN estimated_hours ELSE NULL END, #{average}) " +
1364 "* (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)").to_f
1364 "* (CASE WHEN is_closed = #{connection.quoted_true} THEN 100 ELSE COALESCE(done_ratio, 0) END)").to_f
1365 progress = done / (average * leaves_count)
1365 progress = done / (average * leaves_count)
1366 p.done_ratio = progress.round
1366 p.done_ratio = progress.round
1367 end
1367 end
1368 end
1368 end
1369
1369
1370 # estimate = sum of leaves estimates
1370 # estimate = sum of leaves estimates
1371 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
1371 p.estimated_hours = p.leaves.sum(:estimated_hours).to_f
1372 p.estimated_hours = nil if p.estimated_hours == 0.0
1372 p.estimated_hours = nil if p.estimated_hours == 0.0
1373
1373
1374 # ancestors will be recursively updated
1374 # ancestors will be recursively updated
1375 p.save(:validate => false)
1375 p.save(:validate => false)
1376 end
1376 end
1377 end
1377 end
1378
1378
1379 # Update issues so their versions are not pointing to a
1379 # Update issues so their versions are not pointing to a
1380 # fixed_version that is not shared with the issue's project
1380 # fixed_version that is not shared with the issue's project
1381 def self.update_versions(conditions=nil)
1381 def self.update_versions(conditions=nil)
1382 # Only need to update issues with a fixed_version from
1382 # Only need to update issues with a fixed_version from
1383 # a different project and that is not systemwide shared
1383 # a different project and that is not systemwide shared
1384 Issue.includes(:project, :fixed_version).
1384 Issue.includes(:project, :fixed_version).
1385 where("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
1385 where("#{Issue.table_name}.fixed_version_id IS NOT NULL" +
1386 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
1386 " AND #{Issue.table_name}.project_id <> #{Version.table_name}.project_id" +
1387 " AND #{Version.table_name}.sharing <> 'system'").
1387 " AND #{Version.table_name}.sharing <> 'system'").
1388 where(conditions).each do |issue|
1388 where(conditions).each do |issue|
1389 next if issue.project.nil? || issue.fixed_version.nil?
1389 next if issue.project.nil? || issue.fixed_version.nil?
1390 unless issue.project.shared_versions.include?(issue.fixed_version)
1390 unless issue.project.shared_versions.include?(issue.fixed_version)
1391 issue.init_journal(User.current)
1391 issue.init_journal(User.current)
1392 issue.fixed_version = nil
1392 issue.fixed_version = nil
1393 issue.save
1393 issue.save
1394 end
1394 end
1395 end
1395 end
1396 end
1396 end
1397
1397
1398 # Callback on file attachment
1398 # Callback on file attachment
1399 def attachment_added(obj)
1399 def attachment_added(obj)
1400 if @current_journal && !obj.new_record?
1400 if @current_journal && !obj.new_record?
1401 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
1401 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :value => obj.filename)
1402 end
1402 end
1403 end
1403 end
1404
1404
1405 # Callback on attachment deletion
1405 # Callback on attachment deletion
1406 def attachment_removed(obj)
1406 def attachment_removed(obj)
1407 if @current_journal && !obj.new_record?
1407 if @current_journal && !obj.new_record?
1408 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename)
1408 @current_journal.details << JournalDetail.new(:property => 'attachment', :prop_key => obj.id, :old_value => obj.filename)
1409 @current_journal.save
1409 @current_journal.save
1410 end
1410 end
1411 end
1411 end
1412
1412
1413 # Default assignment based on category
1413 # Default assignment based on category
1414 def default_assign
1414 def default_assign
1415 if assigned_to.nil? && category && category.assigned_to
1415 if assigned_to.nil? && category && category.assigned_to
1416 self.assigned_to = category.assigned_to
1416 self.assigned_to = category.assigned_to
1417 end
1417 end
1418 end
1418 end
1419
1419
1420 # Updates start/due dates of following issues
1420 # Updates start/due dates of following issues
1421 def reschedule_following_issues
1421 def reschedule_following_issues
1422 if start_date_changed? || due_date_changed?
1422 if start_date_changed? || due_date_changed?
1423 relations_from.each do |relation|
1423 relations_from.each do |relation|
1424 relation.set_issue_to_dates
1424 relation.set_issue_to_dates
1425 end
1425 end
1426 end
1426 end
1427 end
1427 end
1428
1428
1429 # Closes duplicates if the issue is being closed
1429 # Closes duplicates if the issue is being closed
1430 def close_duplicates
1430 def close_duplicates
1431 if closing?
1431 if closing?
1432 duplicates.each do |duplicate|
1432 duplicates.each do |duplicate|
1433 # Reload is need in case the duplicate was updated by a previous duplicate
1433 # Reload is need in case the duplicate was updated by a previous duplicate
1434 duplicate.reload
1434 duplicate.reload
1435 # Don't re-close it if it's already closed
1435 # Don't re-close it if it's already closed
1436 next if duplicate.closed?
1436 next if duplicate.closed?
1437 # Same user and notes
1437 # Same user and notes
1438 if @current_journal
1438 if @current_journal
1439 duplicate.init_journal(@current_journal.user, @current_journal.notes)
1439 duplicate.init_journal(@current_journal.user, @current_journal.notes)
1440 end
1440 end
1441 duplicate.update_attribute :status, self.status
1441 duplicate.update_attribute :status, self.status
1442 end
1442 end
1443 end
1443 end
1444 end
1444 end
1445
1445
1446 # Make sure updated_on is updated when adding a note and set updated_on now
1446 # Make sure updated_on is updated when adding a note and set updated_on now
1447 # so we can set closed_on with the same value on closing
1447 # so we can set closed_on with the same value on closing
1448 def force_updated_on_change
1448 def force_updated_on_change
1449 if @current_journal || changed?
1449 if @current_journal || changed?
1450 self.updated_on = current_time_from_proper_timezone
1450 self.updated_on = current_time_from_proper_timezone
1451 if new_record?
1451 if new_record?
1452 self.created_on = updated_on
1452 self.created_on = updated_on
1453 end
1453 end
1454 end
1454 end
1455 end
1455 end
1456
1456
1457 # Callback for setting closed_on when the issue is closed.
1457 # Callback for setting closed_on when the issue is closed.
1458 # The closed_on attribute stores the time of the last closing
1458 # The closed_on attribute stores the time of the last closing
1459 # and is preserved when the issue is reopened.
1459 # and is preserved when the issue is reopened.
1460 def update_closed_on
1460 def update_closed_on
1461 if closing? || (new_record? && closed?)
1461 if closing? || (new_record? && closed?)
1462 self.closed_on = updated_on
1462 self.closed_on = updated_on
1463 end
1463 end
1464 end
1464 end
1465
1465
1466 # Saves the changes in a Journal
1466 # Saves the changes in a Journal
1467 # Called after_save
1467 # Called after_save
1468 def create_journal
1468 def create_journal
1469 if @current_journal
1469 if @current_journal
1470 # attributes changes
1470 # attributes changes
1471 if @attributes_before_change
1471 if @attributes_before_change
1472 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)).each {|c|
1472 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)).each {|c|
1473 before = @attributes_before_change[c]
1473 before = @attributes_before_change[c]
1474 after = send(c)
1474 after = send(c)
1475 next if before == after || (before.blank? && after.blank?)
1475 next if before == after || (before.blank? && after.blank?)
1476 @current_journal.details << JournalDetail.new(:property => 'attr',
1476 @current_journal.details << JournalDetail.new(:property => 'attr',
1477 :prop_key => c,
1477 :prop_key => c,
1478 :old_value => before,
1478 :old_value => before,
1479 :value => after)
1479 :value => after)
1480 }
1480 }
1481 end
1481 end
1482 if @custom_values_before_change
1482 if @custom_values_before_change
1483 # custom fields changes
1483 # custom fields changes
1484 custom_field_values.each {|c|
1484 custom_field_values.each {|c|
1485 before = @custom_values_before_change[c.custom_field_id]
1485 before = @custom_values_before_change[c.custom_field_id]
1486 after = c.value
1486 after = c.value
1487 next if before == after || (before.blank? && after.blank?)
1487 next if before == after || (before.blank? && after.blank?)
1488
1488
1489 if before.is_a?(Array) || after.is_a?(Array)
1489 if before.is_a?(Array) || after.is_a?(Array)
1490 before = [before] unless before.is_a?(Array)
1490 before = [before] unless before.is_a?(Array)
1491 after = [after] unless after.is_a?(Array)
1491 after = [after] unless after.is_a?(Array)
1492
1492
1493 # values removed
1493 # values removed
1494 (before - after).reject(&:blank?).each do |value|
1494 (before - after).reject(&:blank?).each do |value|
1495 @current_journal.details << JournalDetail.new(:property => 'cf',
1495 @current_journal.details << JournalDetail.new(:property => 'cf',
1496 :prop_key => c.custom_field_id,
1496 :prop_key => c.custom_field_id,
1497 :old_value => value,
1497 :old_value => value,
1498 :value => nil)
1498 :value => nil)
1499 end
1499 end
1500 # values added
1500 # values added
1501 (after - before).reject(&:blank?).each do |value|
1501 (after - before).reject(&:blank?).each do |value|
1502 @current_journal.details << JournalDetail.new(:property => 'cf',
1502 @current_journal.details << JournalDetail.new(:property => 'cf',
1503 :prop_key => c.custom_field_id,
1503 :prop_key => c.custom_field_id,
1504 :old_value => nil,
1504 :old_value => nil,
1505 :value => value)
1505 :value => value)
1506 end
1506 end
1507 else
1507 else
1508 @current_journal.details << JournalDetail.new(:property => 'cf',
1508 @current_journal.details << JournalDetail.new(:property => 'cf',
1509 :prop_key => c.custom_field_id,
1509 :prop_key => c.custom_field_id,
1510 :old_value => before,
1510 :old_value => before,
1511 :value => after)
1511 :value => after)
1512 end
1512 end
1513 }
1513 }
1514 end
1514 end
1515 @current_journal.save
1515 @current_journal.save
1516 # reset current journal
1516 # reset current journal
1517 init_journal @current_journal.user, @current_journal.notes
1517 init_journal @current_journal.user, @current_journal.notes
1518 end
1518 end
1519 end
1519 end
1520
1520
1521 def send_notification
1521 def send_notification
1522 if Setting.notified_events.include?('issue_added')
1522 if Setting.notified_events.include?('issue_added')
1523 Mailer.deliver_issue_add(self)
1523 Mailer.deliver_issue_add(self)
1524 end
1524 end
1525 end
1525 end
1526
1526
1527 # Stores the previous assignee so we can still have access
1527 # Stores the previous assignee so we can still have access
1528 # to it during after_save callbacks (assigned_to_id_was is reset)
1528 # to it during after_save callbacks (assigned_to_id_was is reset)
1529 def set_assigned_to_was
1529 def set_assigned_to_was
1530 @previous_assigned_to_id = assigned_to_id_was
1530 @previous_assigned_to_id = assigned_to_id_was
1531 end
1531 end
1532
1532
1533 # Clears the previous assignee at the end of after_save callbacks
1533 # Clears the previous assignee at the end of after_save callbacks
1534 def clear_assigned_to_was
1534 def clear_assigned_to_was
1535 @assigned_to_was = nil
1535 @assigned_to_was = nil
1536 @previous_assigned_to_id = nil
1536 @previous_assigned_to_id = nil
1537 end
1537 end
1538
1538
1539 # Query generator for selecting groups of issue counts for a project
1539 # Query generator for selecting groups of issue counts for a project
1540 # based on specific criteria
1540 # based on specific criteria
1541 #
1541 #
1542 # Options
1542 # Options
1543 # * project - Project to search in.
1543 # * project - Project to search in.
1544 # * field - String. Issue field to key off of in the grouping.
1544 # * field - String. Issue field to key off of in the grouping.
1545 # * joins - String. The table name to join against.
1545 # * joins - String. The table name to join against.
1546 def self.count_and_group_by(options)
1546 def self.count_and_group_by(options)
1547 project = options.delete(:project)
1547 project = options.delete(:project)
1548 select_field = options.delete(:field)
1548 select_field = options.delete(:field)
1549 joins = options.delete(:joins)
1549 joins = options.delete(:joins)
1550
1550
1551 where = "#{Issue.table_name}.#{select_field}=j.id"
1551 where = "#{Issue.table_name}.#{select_field}=j.id"
1552
1552
1553 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1553 ActiveRecord::Base.connection.select_all("select s.id as status_id,
1554 s.is_closed as closed,
1554 s.is_closed as closed,
1555 j.id as #{select_field},
1555 j.id as #{select_field},
1556 count(#{Issue.table_name}.id) as total
1556 count(#{Issue.table_name}.id) as total
1557 from
1557 from
1558 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
1558 #{Issue.table_name}, #{Project.table_name}, #{IssueStatus.table_name} s, #{joins} j
1559 where
1559 where
1560 #{Issue.table_name}.status_id=s.id
1560 #{Issue.table_name}.status_id=s.id
1561 and #{where}
1561 and #{where}
1562 and #{Issue.table_name}.project_id=#{Project.table_name}.id
1562 and #{Issue.table_name}.project_id=#{Project.table_name}.id
1563 and #{visible_condition(User.current, :project => project)}
1563 and #{visible_condition(User.current, :project => project)}
1564 group by s.id, s.is_closed, j.id")
1564 group by s.id, s.is_closed, j.id")
1565 end
1565 end
1566 end
1566 end
@@ -1,2355 +1,2358
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class IssueTest < ActiveSupport::TestCase
20 class IssueTest < ActiveSupport::TestCase
21 fixtures :projects, :users, :members, :member_roles, :roles,
21 fixtures :projects, :users, :members, :member_roles, :roles,
22 :groups_users,
22 :groups_users,
23 :trackers, :projects_trackers,
23 :trackers, :projects_trackers,
24 :enabled_modules,
24 :enabled_modules,
25 :versions,
25 :versions,
26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
26 :issue_statuses, :issue_categories, :issue_relations, :workflows,
27 :enumerations,
27 :enumerations,
28 :issues, :journals, :journal_details,
28 :issues, :journals, :journal_details,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
29 :custom_fields, :custom_fields_projects, :custom_fields_trackers, :custom_values,
30 :time_entries
30 :time_entries
31
31
32 include Redmine::I18n
32 include Redmine::I18n
33
33
34 def teardown
34 def teardown
35 User.current = nil
35 User.current = nil
36 end
36 end
37
37
38 def test_initialize
38 def test_initialize
39 issue = Issue.new
39 issue = Issue.new
40
40
41 assert_nil issue.project_id
41 assert_nil issue.project_id
42 assert_nil issue.tracker_id
42 assert_nil issue.tracker_id
43 assert_nil issue.author_id
43 assert_nil issue.author_id
44 assert_nil issue.assigned_to_id
44 assert_nil issue.assigned_to_id
45 assert_nil issue.category_id
45 assert_nil issue.category_id
46
46
47 assert_equal IssueStatus.default, issue.status
47 assert_equal IssueStatus.default, issue.status
48 assert_equal IssuePriority.default, issue.priority
48 assert_equal IssuePriority.default, issue.priority
49 end
49 end
50
50
51 def test_create
51 def test_create
52 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
52 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
53 :status_id => 1, :priority => IssuePriority.all.first,
53 :status_id => 1, :priority => IssuePriority.all.first,
54 :subject => 'test_create',
54 :subject => 'test_create',
55 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
55 :description => 'IssueTest#test_create', :estimated_hours => '1:30')
56 assert issue.save
56 assert issue.save
57 issue.reload
57 issue.reload
58 assert_equal 1.5, issue.estimated_hours
58 assert_equal 1.5, issue.estimated_hours
59 end
59 end
60
60
61 def test_create_minimal
61 def test_create_minimal
62 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
62 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 3,
63 :status_id => 1, :priority => IssuePriority.all.first,
63 :status_id => 1, :priority => IssuePriority.all.first,
64 :subject => 'test_create')
64 :subject => 'test_create')
65 assert issue.save
65 assert issue.save
66 assert issue.description.nil?
66 assert issue.description.nil?
67 assert_nil issue.estimated_hours
67 assert_nil issue.estimated_hours
68 end
68 end
69
69
70 def test_start_date_format_should_be_validated
70 def test_start_date_format_should_be_validated
71 set_language_if_valid 'en'
71 set_language_if_valid 'en'
72 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
72 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
73 issue = Issue.new(:start_date => invalid_date)
73 issue = Issue.new(:start_date => invalid_date)
74 assert !issue.valid?
74 assert !issue.valid?
75 assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
75 assert_include 'Start date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
76 end
76 end
77 end
77 end
78
78
79 def test_due_date_format_should_be_validated
79 def test_due_date_format_should_be_validated
80 set_language_if_valid 'en'
80 set_language_if_valid 'en'
81 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
81 ['2012', 'ABC', '2012-15-20'].each do |invalid_date|
82 issue = Issue.new(:due_date => invalid_date)
82 issue = Issue.new(:due_date => invalid_date)
83 assert !issue.valid?
83 assert !issue.valid?
84 assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
84 assert_include 'Due date is not a valid date', issue.errors.full_messages, "No error found for invalid date #{invalid_date}"
85 end
85 end
86 end
86 end
87
87
88 def test_due_date_lesser_than_start_date_should_not_validate
88 def test_due_date_lesser_than_start_date_should_not_validate
89 set_language_if_valid 'en'
89 set_language_if_valid 'en'
90 issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02')
90 issue = Issue.new(:start_date => '2012-10-06', :due_date => '2012-10-02')
91 assert !issue.valid?
91 assert !issue.valid?
92 assert_include 'Due date must be greater than start date', issue.errors.full_messages
92 assert_include 'Due date must be greater than start date', issue.errors.full_messages
93 end
93 end
94
94
95 def test_start_date_lesser_than_soonest_start_should_not_validate_on_create
95 def test_start_date_lesser_than_soonest_start_should_not_validate_on_create
96 issue = Issue.generate(:start_date => '2013-06-04')
96 issue = Issue.generate(:start_date => '2013-06-04')
97 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
97 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
98 assert !issue.valid?
98 assert !issue.valid?
99 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
99 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
100 end
100 end
101
101
102 def test_start_date_lesser_than_soonest_start_should_not_validate_on_update_if_changed
102 def test_start_date_lesser_than_soonest_start_should_not_validate_on_update_if_changed
103 issue = Issue.generate!(:start_date => '2013-06-04')
103 issue = Issue.generate!(:start_date => '2013-06-04')
104 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
104 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
105 issue.start_date = '2013-06-07'
105 issue.start_date = '2013-06-07'
106 assert !issue.valid?
106 assert !issue.valid?
107 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
107 assert_include "Start date cannot be earlier than 06/10/2013 because of preceding issues", issue.errors.full_messages
108 end
108 end
109
109
110 def test_start_date_lesser_than_soonest_start_should_validate_on_update_if_unchanged
110 def test_start_date_lesser_than_soonest_start_should_validate_on_update_if_unchanged
111 issue = Issue.generate!(:start_date => '2013-06-04')
111 issue = Issue.generate!(:start_date => '2013-06-04')
112 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
112 issue.stubs(:soonest_start).returns(Date.parse('2013-06-10'))
113 assert issue.valid?
113 assert issue.valid?
114 end
114 end
115
115
116 def test_estimated_hours_should_be_validated
116 def test_estimated_hours_should_be_validated
117 set_language_if_valid 'en'
117 set_language_if_valid 'en'
118 ['-2'].each do |invalid|
118 ['-2'].each do |invalid|
119 issue = Issue.new(:estimated_hours => invalid)
119 issue = Issue.new(:estimated_hours => invalid)
120 assert !issue.valid?
120 assert !issue.valid?
121 assert_include 'Estimated time is invalid', issue.errors.full_messages
121 assert_include 'Estimated time is invalid', issue.errors.full_messages
122 end
122 end
123 end
123 end
124
124
125 def test_create_with_required_custom_field
125 def test_create_with_required_custom_field
126 set_language_if_valid 'en'
126 set_language_if_valid 'en'
127 field = IssueCustomField.find_by_name('Database')
127 field = IssueCustomField.find_by_name('Database')
128 field.update_attribute(:is_required, true)
128 field.update_attribute(:is_required, true)
129
129
130 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
130 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
131 :status_id => 1, :subject => 'test_create',
131 :status_id => 1, :subject => 'test_create',
132 :description => 'IssueTest#test_create_with_required_custom_field')
132 :description => 'IssueTest#test_create_with_required_custom_field')
133 assert issue.available_custom_fields.include?(field)
133 assert issue.available_custom_fields.include?(field)
134 # No value for the custom field
134 # No value for the custom field
135 assert !issue.save
135 assert !issue.save
136 assert_equal ["Database can't be blank"], issue.errors.full_messages
136 assert_equal ["Database can't be blank"], issue.errors.full_messages
137 # Blank value
137 # Blank value
138 issue.custom_field_values = { field.id => '' }
138 issue.custom_field_values = { field.id => '' }
139 assert !issue.save
139 assert !issue.save
140 assert_equal ["Database can't be blank"], issue.errors.full_messages
140 assert_equal ["Database can't be blank"], issue.errors.full_messages
141 # Invalid value
141 # Invalid value
142 issue.custom_field_values = { field.id => 'SQLServer' }
142 issue.custom_field_values = { field.id => 'SQLServer' }
143 assert !issue.save
143 assert !issue.save
144 assert_equal ["Database is not included in the list"], issue.errors.full_messages
144 assert_equal ["Database is not included in the list"], issue.errors.full_messages
145 # Valid value
145 # Valid value
146 issue.custom_field_values = { field.id => 'PostgreSQL' }
146 issue.custom_field_values = { field.id => 'PostgreSQL' }
147 assert issue.save
147 assert issue.save
148 issue.reload
148 issue.reload
149 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
149 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
150 end
150 end
151
151
152 def test_create_with_group_assignment
152 def test_create_with_group_assignment
153 with_settings :issue_group_assignment => '1' do
153 with_settings :issue_group_assignment => '1' do
154 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
154 assert Issue.new(:project_id => 2, :tracker_id => 1, :author_id => 1,
155 :subject => 'Group assignment',
155 :subject => 'Group assignment',
156 :assigned_to_id => 11).save
156 :assigned_to_id => 11).save
157 issue = Issue.order('id DESC').first
157 issue = Issue.order('id DESC').first
158 assert_kind_of Group, issue.assigned_to
158 assert_kind_of Group, issue.assigned_to
159 assert_equal Group.find(11), issue.assigned_to
159 assert_equal Group.find(11), issue.assigned_to
160 end
160 end
161 end
161 end
162
162
163 def test_create_with_parent_issue_id
163 def test_create_with_parent_issue_id
164 issue = Issue.new(:project_id => 1, :tracker_id => 1,
164 issue = Issue.new(:project_id => 1, :tracker_id => 1,
165 :author_id => 1, :subject => 'Group assignment',
165 :author_id => 1, :subject => 'Group assignment',
166 :parent_issue_id => 1)
166 :parent_issue_id => 1)
167 assert_save issue
167 assert_save issue
168 assert_equal 1, issue.parent_issue_id
168 assert_equal 1, issue.parent_issue_id
169 assert_equal Issue.find(1), issue.parent
169 assert_equal Issue.find(1), issue.parent
170 end
170 end
171
171
172 def test_create_with_sharp_parent_issue_id
172 def test_create_with_sharp_parent_issue_id
173 issue = Issue.new(:project_id => 1, :tracker_id => 1,
173 issue = Issue.new(:project_id => 1, :tracker_id => 1,
174 :author_id => 1, :subject => 'Group assignment',
174 :author_id => 1, :subject => 'Group assignment',
175 :parent_issue_id => "#1")
175 :parent_issue_id => "#1")
176 assert_save issue
176 assert_save issue
177 assert_equal 1, issue.parent_issue_id
177 assert_equal 1, issue.parent_issue_id
178 assert_equal Issue.find(1), issue.parent
178 assert_equal Issue.find(1), issue.parent
179 end
179 end
180
180
181 def test_create_with_invalid_parent_issue_id
181 def test_create_with_invalid_parent_issue_id
182 set_language_if_valid 'en'
182 set_language_if_valid 'en'
183 issue = Issue.new(:project_id => 1, :tracker_id => 1,
183 issue = Issue.new(:project_id => 1, :tracker_id => 1,
184 :author_id => 1, :subject => 'Group assignment',
184 :author_id => 1, :subject => 'Group assignment',
185 :parent_issue_id => '01ABC')
185 :parent_issue_id => '01ABC')
186 assert !issue.save
186 assert !issue.save
187 assert_equal '01ABC', issue.parent_issue_id
187 assert_equal '01ABC', issue.parent_issue_id
188 assert_include 'Parent task is invalid', issue.errors.full_messages
188 assert_include 'Parent task is invalid', issue.errors.full_messages
189 end
189 end
190
190
191 def test_create_with_invalid_sharp_parent_issue_id
191 def test_create_with_invalid_sharp_parent_issue_id
192 set_language_if_valid 'en'
192 set_language_if_valid 'en'
193 issue = Issue.new(:project_id => 1, :tracker_id => 1,
193 issue = Issue.new(:project_id => 1, :tracker_id => 1,
194 :author_id => 1, :subject => 'Group assignment',
194 :author_id => 1, :subject => 'Group assignment',
195 :parent_issue_id => '#01ABC')
195 :parent_issue_id => '#01ABC')
196 assert !issue.save
196 assert !issue.save
197 assert_equal '#01ABC', issue.parent_issue_id
197 assert_equal '#01ABC', issue.parent_issue_id
198 assert_include 'Parent task is invalid', issue.errors.full_messages
198 assert_include 'Parent task is invalid', issue.errors.full_messages
199 end
199 end
200
200
201 def assert_visibility_match(user, issues)
201 def assert_visibility_match(user, issues)
202 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
202 assert_equal issues.collect(&:id).sort, Issue.all.select {|issue| issue.visible?(user)}.collect(&:id).sort
203 end
203 end
204
204
205 def test_visible_scope_for_anonymous
205 def test_visible_scope_for_anonymous
206 # Anonymous user should see issues of public projects only
206 # Anonymous user should see issues of public projects only
207 issues = Issue.visible(User.anonymous).all
207 issues = Issue.visible(User.anonymous).all
208 assert issues.any?
208 assert issues.any?
209 assert_nil issues.detect {|issue| !issue.project.is_public?}
209 assert_nil issues.detect {|issue| !issue.project.is_public?}
210 assert_nil issues.detect {|issue| issue.is_private?}
210 assert_nil issues.detect {|issue| issue.is_private?}
211 assert_visibility_match User.anonymous, issues
211 assert_visibility_match User.anonymous, issues
212 end
212 end
213
213
214 def test_visible_scope_for_anonymous_without_view_issues_permissions
214 def test_visible_scope_for_anonymous_without_view_issues_permissions
215 # Anonymous user should not see issues without permission
215 # Anonymous user should not see issues without permission
216 Role.anonymous.remove_permission!(:view_issues)
216 Role.anonymous.remove_permission!(:view_issues)
217 issues = Issue.visible(User.anonymous).all
217 issues = Issue.visible(User.anonymous).all
218 assert issues.empty?
218 assert issues.empty?
219 assert_visibility_match User.anonymous, issues
219 assert_visibility_match User.anonymous, issues
220 end
220 end
221
221
222 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
222 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_default
223 assert Role.anonymous.update_attribute(:issues_visibility, 'default')
223 assert Role.anonymous.update_attribute(:issues_visibility, 'default')
224 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
224 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
225 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
225 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
226 assert !issue.visible?(User.anonymous)
226 assert !issue.visible?(User.anonymous)
227 end
227 end
228
228
229 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
229 def test_anonymous_should_not_see_private_issues_with_issues_visibility_set_to_own
230 assert Role.anonymous.update_attribute(:issues_visibility, 'own')
230 assert Role.anonymous.update_attribute(:issues_visibility, 'own')
231 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
231 issue = Issue.generate!(:author => User.anonymous, :assigned_to => User.anonymous, :is_private => true)
232 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
232 assert_nil Issue.where(:id => issue.id).visible(User.anonymous).first
233 assert !issue.visible?(User.anonymous)
233 assert !issue.visible?(User.anonymous)
234 end
234 end
235
235
236 def test_visible_scope_for_non_member
236 def test_visible_scope_for_non_member
237 user = User.find(9)
237 user = User.find(9)
238 assert user.projects.empty?
238 assert user.projects.empty?
239 # Non member user should see issues of public projects only
239 # Non member user should see issues of public projects only
240 issues = Issue.visible(user).all
240 issues = Issue.visible(user).all
241 assert issues.any?
241 assert issues.any?
242 assert_nil issues.detect {|issue| !issue.project.is_public?}
242 assert_nil issues.detect {|issue| !issue.project.is_public?}
243 assert_nil issues.detect {|issue| issue.is_private?}
243 assert_nil issues.detect {|issue| issue.is_private?}
244 assert_visibility_match user, issues
244 assert_visibility_match user, issues
245 end
245 end
246
246
247 def test_visible_scope_for_non_member_with_own_issues_visibility
247 def test_visible_scope_for_non_member_with_own_issues_visibility
248 Role.non_member.update_attribute :issues_visibility, 'own'
248 Role.non_member.update_attribute :issues_visibility, 'own'
249 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
249 Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 9, :subject => 'Issue by non member')
250 user = User.find(9)
250 user = User.find(9)
251
251
252 issues = Issue.visible(user).all
252 issues = Issue.visible(user).all
253 assert issues.any?
253 assert issues.any?
254 assert_nil issues.detect {|issue| issue.author != user}
254 assert_nil issues.detect {|issue| issue.author != user}
255 assert_visibility_match user, issues
255 assert_visibility_match user, issues
256 end
256 end
257
257
258 def test_visible_scope_for_non_member_without_view_issues_permissions
258 def test_visible_scope_for_non_member_without_view_issues_permissions
259 # Non member user should not see issues without permission
259 # Non member user should not see issues without permission
260 Role.non_member.remove_permission!(:view_issues)
260 Role.non_member.remove_permission!(:view_issues)
261 user = User.find(9)
261 user = User.find(9)
262 assert user.projects.empty?
262 assert user.projects.empty?
263 issues = Issue.visible(user).all
263 issues = Issue.visible(user).all
264 assert issues.empty?
264 assert issues.empty?
265 assert_visibility_match user, issues
265 assert_visibility_match user, issues
266 end
266 end
267
267
268 def test_visible_scope_for_member
268 def test_visible_scope_for_member
269 user = User.find(9)
269 user = User.find(9)
270 # User should see issues of projects for which user has view_issues permissions only
270 # User should see issues of projects for which user has view_issues permissions only
271 Role.non_member.remove_permission!(:view_issues)
271 Role.non_member.remove_permission!(:view_issues)
272 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
272 Member.create!(:principal => user, :project_id => 3, :role_ids => [2])
273 issues = Issue.visible(user).all
273 issues = Issue.visible(user).all
274 assert issues.any?
274 assert issues.any?
275 assert_nil issues.detect {|issue| issue.project_id != 3}
275 assert_nil issues.detect {|issue| issue.project_id != 3}
276 assert_nil issues.detect {|issue| issue.is_private?}
276 assert_nil issues.detect {|issue| issue.is_private?}
277 assert_visibility_match user, issues
277 assert_visibility_match user, issues
278 end
278 end
279
279
280 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
280 def test_visible_scope_for_member_with_groups_should_return_assigned_issues
281 user = User.find(8)
281 user = User.find(8)
282 assert user.groups.any?
282 assert user.groups.any?
283 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
283 Member.create!(:principal => user.groups.first, :project_id => 1, :role_ids => [2])
284 Role.non_member.remove_permission!(:view_issues)
284 Role.non_member.remove_permission!(:view_issues)
285
285
286 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
286 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
287 :status_id => 1, :priority => IssuePriority.all.first,
287 :status_id => 1, :priority => IssuePriority.all.first,
288 :subject => 'Assignment test',
288 :subject => 'Assignment test',
289 :assigned_to => user.groups.first,
289 :assigned_to => user.groups.first,
290 :is_private => true)
290 :is_private => true)
291
291
292 Role.find(2).update_attribute :issues_visibility, 'default'
292 Role.find(2).update_attribute :issues_visibility, 'default'
293 issues = Issue.visible(User.find(8)).all
293 issues = Issue.visible(User.find(8)).all
294 assert issues.any?
294 assert issues.any?
295 assert issues.include?(issue)
295 assert issues.include?(issue)
296
296
297 Role.find(2).update_attribute :issues_visibility, 'own'
297 Role.find(2).update_attribute :issues_visibility, 'own'
298 issues = Issue.visible(User.find(8)).all
298 issues = Issue.visible(User.find(8)).all
299 assert issues.any?
299 assert issues.any?
300 assert issues.include?(issue)
300 assert issues.include?(issue)
301 end
301 end
302
302
303 def test_visible_scope_for_admin
303 def test_visible_scope_for_admin
304 user = User.find(1)
304 user = User.find(1)
305 user.members.each(&:destroy)
305 user.members.each(&:destroy)
306 assert user.projects.empty?
306 assert user.projects.empty?
307 issues = Issue.visible(user).all
307 issues = Issue.visible(user).all
308 assert issues.any?
308 assert issues.any?
309 # Admin should see issues on private projects that admin does not belong to
309 # Admin should see issues on private projects that admin does not belong to
310 assert issues.detect {|issue| !issue.project.is_public?}
310 assert issues.detect {|issue| !issue.project.is_public?}
311 # Admin should see private issues of other users
311 # Admin should see private issues of other users
312 assert issues.detect {|issue| issue.is_private? && issue.author != user}
312 assert issues.detect {|issue| issue.is_private? && issue.author != user}
313 assert_visibility_match user, issues
313 assert_visibility_match user, issues
314 end
314 end
315
315
316 def test_visible_scope_with_project
316 def test_visible_scope_with_project
317 project = Project.find(1)
317 project = Project.find(1)
318 issues = Issue.visible(User.find(2), :project => project).all
318 issues = Issue.visible(User.find(2), :project => project).all
319 projects = issues.collect(&:project).uniq
319 projects = issues.collect(&:project).uniq
320 assert_equal 1, projects.size
320 assert_equal 1, projects.size
321 assert_equal project, projects.first
321 assert_equal project, projects.first
322 end
322 end
323
323
324 def test_visible_scope_with_project_and_subprojects
324 def test_visible_scope_with_project_and_subprojects
325 project = Project.find(1)
325 project = Project.find(1)
326 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
326 issues = Issue.visible(User.find(2), :project => project, :with_subprojects => true).all
327 projects = issues.collect(&:project).uniq
327 projects = issues.collect(&:project).uniq
328 assert projects.size > 1
328 assert projects.size > 1
329 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
329 assert_equal [], projects.select {|p| !p.is_or_is_descendant_of?(project)}
330 end
330 end
331
331
332 def test_visible_and_nested_set_scopes
332 def test_visible_and_nested_set_scopes
333 user = User.generate!
333 user = User.generate!
334 parent = Issue.generate!(:assigned_to => user)
334 parent = Issue.generate!(:assigned_to => user)
335 assert parent.visible?(user)
335 assert parent.visible?(user)
336 child1 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
336 child1 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
337 child2 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
337 child2 = Issue.generate!(:parent_issue_id => parent.id, :assigned_to => user)
338 parent.reload
338 parent.reload
339 child1.reload
339 child1.reload
340 child2.reload
340 child2.reload
341 assert child1.visible?(user)
341 assert child1.visible?(user)
342 assert child2.visible?(user)
342 assert child2.visible?(user)
343 assert_equal 2, parent.descendants.count
343 assert_equal 2, parent.descendants.count
344 assert_equal 2, parent.descendants.visible(user).count
344 assert_equal 2, parent.descendants.visible(user).count
345 # awesome_nested_set 2-1-stable branch has regression.
345 # awesome_nested_set 2-1-stable branch has regression.
346 # https://github.com/collectiveidea/awesome_nested_set/commit/3d5ac746542b564f6586c2316180254b088bebb6
346 # https://github.com/collectiveidea/awesome_nested_set/commit/3d5ac746542b564f6586c2316180254b088bebb6
347 # ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: lft:
347 # ActiveRecord::StatementInvalid: SQLite3::SQLException: ambiguous column name: lft:
348 assert_equal 2, parent.descendants.collect{|i| i}.size
348 assert_equal 2, parent.descendants.collect{|i| i}.size
349 assert_equal 2, parent.descendants.visible(user).collect{|i| i}.size
349 assert_equal 2, parent.descendants.visible(user).collect{|i| i}.size
350 end
350 end
351
351
352 def test_open_scope
352 def test_open_scope
353 issues = Issue.open.all
353 issues = Issue.open.all
354 assert_nil issues.detect(&:closed?)
354 assert_nil issues.detect(&:closed?)
355 end
355 end
356
356
357 def test_open_scope_with_arg
357 def test_open_scope_with_arg
358 issues = Issue.open(false).all
358 issues = Issue.open(false).all
359 assert_equal issues, issues.select(&:closed?)
359 assert_equal issues, issues.select(&:closed?)
360 end
360 end
361
361
362 def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
362 def test_fixed_version_scope_with_a_version_should_return_its_fixed_issues
363 version = Version.find(2)
363 version = Version.find(2)
364 assert version.fixed_issues.any?
364 assert version.fixed_issues.any?
365 assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
365 assert_equal version.fixed_issues.to_a.sort, Issue.fixed_version(version).to_a.sort
366 end
366 end
367
367
368 def test_fixed_version_scope_with_empty_array_should_return_no_result
368 def test_fixed_version_scope_with_empty_array_should_return_no_result
369 assert_equal 0, Issue.fixed_version([]).count
369 assert_equal 0, Issue.fixed_version([]).count
370 end
370 end
371
371
372 def test_errors_full_messages_should_include_custom_fields_errors
372 def test_errors_full_messages_should_include_custom_fields_errors
373 field = IssueCustomField.find_by_name('Database')
373 field = IssueCustomField.find_by_name('Database')
374
374
375 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
375 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
376 :status_id => 1, :subject => 'test_create',
376 :status_id => 1, :subject => 'test_create',
377 :description => 'IssueTest#test_create_with_required_custom_field')
377 :description => 'IssueTest#test_create_with_required_custom_field')
378 assert issue.available_custom_fields.include?(field)
378 assert issue.available_custom_fields.include?(field)
379 # Invalid value
379 # Invalid value
380 issue.custom_field_values = { field.id => 'SQLServer' }
380 issue.custom_field_values = { field.id => 'SQLServer' }
381
381
382 assert !issue.valid?
382 assert !issue.valid?
383 assert_equal 1, issue.errors.full_messages.size
383 assert_equal 1, issue.errors.full_messages.size
384 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
384 assert_equal "Database #{I18n.translate('activerecord.errors.messages.inclusion')}",
385 issue.errors.full_messages.first
385 issue.errors.full_messages.first
386 end
386 end
387
387
388 def test_update_issue_with_required_custom_field
388 def test_update_issue_with_required_custom_field
389 field = IssueCustomField.find_by_name('Database')
389 field = IssueCustomField.find_by_name('Database')
390 field.update_attribute(:is_required, true)
390 field.update_attribute(:is_required, true)
391
391
392 issue = Issue.find(1)
392 issue = Issue.find(1)
393 assert_nil issue.custom_value_for(field)
393 assert_nil issue.custom_value_for(field)
394 assert issue.available_custom_fields.include?(field)
394 assert issue.available_custom_fields.include?(field)
395 # No change to custom values, issue can be saved
395 # No change to custom values, issue can be saved
396 assert issue.save
396 assert issue.save
397 # Blank value
397 # Blank value
398 issue.custom_field_values = { field.id => '' }
398 issue.custom_field_values = { field.id => '' }
399 assert !issue.save
399 assert !issue.save
400 # Valid value
400 # Valid value
401 issue.custom_field_values = { field.id => 'PostgreSQL' }
401 issue.custom_field_values = { field.id => 'PostgreSQL' }
402 assert issue.save
402 assert issue.save
403 issue.reload
403 issue.reload
404 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
404 assert_equal 'PostgreSQL', issue.custom_value_for(field).value
405 end
405 end
406
406
407 def test_should_not_update_attributes_if_custom_fields_validation_fails
407 def test_should_not_update_attributes_if_custom_fields_validation_fails
408 issue = Issue.find(1)
408 issue = Issue.find(1)
409 field = IssueCustomField.find_by_name('Database')
409 field = IssueCustomField.find_by_name('Database')
410 assert issue.available_custom_fields.include?(field)
410 assert issue.available_custom_fields.include?(field)
411
411
412 issue.custom_field_values = { field.id => 'Invalid' }
412 issue.custom_field_values = { field.id => 'Invalid' }
413 issue.subject = 'Should be not be saved'
413 issue.subject = 'Should be not be saved'
414 assert !issue.save
414 assert !issue.save
415
415
416 issue.reload
416 issue.reload
417 assert_equal "Can't print recipes", issue.subject
417 assert_equal "Can't print recipes", issue.subject
418 end
418 end
419
419
420 def test_should_not_recreate_custom_values_objects_on_update
420 def test_should_not_recreate_custom_values_objects_on_update
421 field = IssueCustomField.find_by_name('Database')
421 field = IssueCustomField.find_by_name('Database')
422
422
423 issue = Issue.find(1)
423 issue = Issue.find(1)
424 issue.custom_field_values = { field.id => 'PostgreSQL' }
424 issue.custom_field_values = { field.id => 'PostgreSQL' }
425 assert issue.save
425 assert issue.save
426 custom_value = issue.custom_value_for(field)
426 custom_value = issue.custom_value_for(field)
427 issue.reload
427 issue.reload
428 issue.custom_field_values = { field.id => 'MySQL' }
428 issue.custom_field_values = { field.id => 'MySQL' }
429 assert issue.save
429 assert issue.save
430 issue.reload
430 issue.reload
431 assert_equal custom_value.id, issue.custom_value_for(field).id
431 assert_equal custom_value.id, issue.custom_value_for(field).id
432 end
432 end
433
433
434 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
434 def test_should_not_update_custom_fields_on_changing_tracker_with_different_custom_fields
435 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1,
435 issue = Issue.create!(:project_id => 1, :tracker_id => 1, :author_id => 1,
436 :status_id => 1, :subject => 'Test',
436 :status_id => 1, :subject => 'Test',
437 :custom_field_values => {'2' => 'Test'})
437 :custom_field_values => {'2' => 'Test'})
438 assert !Tracker.find(2).custom_field_ids.include?(2)
438 assert !Tracker.find(2).custom_field_ids.include?(2)
439
439
440 issue = Issue.find(issue.id)
440 issue = Issue.find(issue.id)
441 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
441 issue.attributes = {:tracker_id => 2, :custom_field_values => {'1' => ''}}
442
442
443 issue = Issue.find(issue.id)
443 issue = Issue.find(issue.id)
444 custom_value = issue.custom_value_for(2)
444 custom_value = issue.custom_value_for(2)
445 assert_not_nil custom_value
445 assert_not_nil custom_value
446 assert_equal 'Test', custom_value.value
446 assert_equal 'Test', custom_value.value
447 end
447 end
448
448
449 def test_assigning_tracker_id_should_reload_custom_fields_values
449 def test_assigning_tracker_id_should_reload_custom_fields_values
450 issue = Issue.new(:project => Project.find(1))
450 issue = Issue.new(:project => Project.find(1))
451 assert issue.custom_field_values.empty?
451 assert issue.custom_field_values.empty?
452 issue.tracker_id = 1
452 issue.tracker_id = 1
453 assert issue.custom_field_values.any?
453 assert issue.custom_field_values.any?
454 end
454 end
455
455
456 def test_assigning_attributes_should_assign_project_and_tracker_first
456 def test_assigning_attributes_should_assign_project_and_tracker_first
457 seq = sequence('seq')
457 seq = sequence('seq')
458 issue = Issue.new
458 issue = Issue.new
459 issue.expects(:project_id=).in_sequence(seq)
459 issue.expects(:project_id=).in_sequence(seq)
460 issue.expects(:tracker_id=).in_sequence(seq)
460 issue.expects(:tracker_id=).in_sequence(seq)
461 issue.expects(:subject=).in_sequence(seq)
461 issue.expects(:subject=).in_sequence(seq)
462 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
462 issue.attributes = {:tracker_id => 2, :project_id => 1, :subject => 'Test'}
463 end
463 end
464
464
465 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
465 def test_assigning_tracker_and_custom_fields_should_assign_custom_fields
466 attributes = ActiveSupport::OrderedHash.new
466 attributes = ActiveSupport::OrderedHash.new
467 attributes['custom_field_values'] = { '1' => 'MySQL' }
467 attributes['custom_field_values'] = { '1' => 'MySQL' }
468 attributes['tracker_id'] = '1'
468 attributes['tracker_id'] = '1'
469 issue = Issue.new(:project => Project.find(1))
469 issue = Issue.new(:project => Project.find(1))
470 issue.attributes = attributes
470 issue.attributes = attributes
471 assert_equal 'MySQL', issue.custom_field_value(1)
471 assert_equal 'MySQL', issue.custom_field_value(1)
472 end
472 end
473
473
474 def test_reload_should_reload_custom_field_values
474 def test_reload_should_reload_custom_field_values
475 issue = Issue.generate!
475 issue = Issue.generate!
476 issue.custom_field_values = {'2' => 'Foo'}
476 issue.custom_field_values = {'2' => 'Foo'}
477 issue.save!
477 issue.save!
478
478
479 issue = Issue.order('id desc').first
479 issue = Issue.order('id desc').first
480 assert_equal 'Foo', issue.custom_field_value(2)
480 assert_equal 'Foo', issue.custom_field_value(2)
481
481
482 issue.custom_field_values = {'2' => 'Bar'}
482 issue.custom_field_values = {'2' => 'Bar'}
483 assert_equal 'Bar', issue.custom_field_value(2)
483 assert_equal 'Bar', issue.custom_field_value(2)
484
484
485 issue.reload
485 issue.reload
486 assert_equal 'Foo', issue.custom_field_value(2)
486 assert_equal 'Foo', issue.custom_field_value(2)
487 end
487 end
488
488
489 def test_should_update_issue_with_disabled_tracker
489 def test_should_update_issue_with_disabled_tracker
490 p = Project.find(1)
490 p = Project.find(1)
491 issue = Issue.find(1)
491 issue = Issue.find(1)
492
492
493 p.trackers.delete(issue.tracker)
493 p.trackers.delete(issue.tracker)
494 assert !p.trackers.include?(issue.tracker)
494 assert !p.trackers.include?(issue.tracker)
495
495
496 issue.reload
496 issue.reload
497 issue.subject = 'New subject'
497 issue.subject = 'New subject'
498 assert issue.save
498 assert issue.save
499 end
499 end
500
500
501 def test_should_not_set_a_disabled_tracker
501 def test_should_not_set_a_disabled_tracker
502 p = Project.find(1)
502 p = Project.find(1)
503 p.trackers.delete(Tracker.find(2))
503 p.trackers.delete(Tracker.find(2))
504
504
505 issue = Issue.find(1)
505 issue = Issue.find(1)
506 issue.tracker_id = 2
506 issue.tracker_id = 2
507 issue.subject = 'New subject'
507 issue.subject = 'New subject'
508 assert !issue.save
508 assert !issue.save
509 assert_not_equal [], issue.errors[:tracker_id]
509 assert_not_equal [], issue.errors[:tracker_id]
510 end
510 end
511
511
512 def test_category_based_assignment
512 def test_category_based_assignment
513 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
513 issue = Issue.create(:project_id => 1, :tracker_id => 1, :author_id => 3,
514 :status_id => 1, :priority => IssuePriority.all.first,
514 :status_id => 1, :priority => IssuePriority.all.first,
515 :subject => 'Assignment test',
515 :subject => 'Assignment test',
516 :description => 'Assignment test', :category_id => 1)
516 :description => 'Assignment test', :category_id => 1)
517 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
517 assert_equal IssueCategory.find(1).assigned_to, issue.assigned_to
518 end
518 end
519
519
520 def test_new_statuses_allowed_to
520 def test_new_statuses_allowed_to
521 WorkflowTransition.delete_all
521 WorkflowTransition.delete_all
522 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
522 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
523 :old_status_id => 1, :new_status_id => 2,
523 :old_status_id => 1, :new_status_id => 2,
524 :author => false, :assignee => false)
524 :author => false, :assignee => false)
525 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
525 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
526 :old_status_id => 1, :new_status_id => 3,
526 :old_status_id => 1, :new_status_id => 3,
527 :author => true, :assignee => false)
527 :author => true, :assignee => false)
528 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
528 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
529 :old_status_id => 1, :new_status_id => 4,
529 :old_status_id => 1, :new_status_id => 4,
530 :author => false, :assignee => true)
530 :author => false, :assignee => true)
531 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
531 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
532 :old_status_id => 1, :new_status_id => 5,
532 :old_status_id => 1, :new_status_id => 5,
533 :author => true, :assignee => true)
533 :author => true, :assignee => true)
534 status = IssueStatus.find(1)
534 status = IssueStatus.find(1)
535 role = Role.find(1)
535 role = Role.find(1)
536 tracker = Tracker.find(1)
536 tracker = Tracker.find(1)
537 user = User.find(2)
537 user = User.find(2)
538
538
539 issue = Issue.generate!(:tracker => tracker, :status => status,
539 issue = Issue.generate!(:tracker => tracker, :status => status,
540 :project_id => 1, :author_id => 1)
540 :project_id => 1, :author_id => 1)
541 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
541 assert_equal [1, 2], issue.new_statuses_allowed_to(user).map(&:id)
542
542
543 issue = Issue.generate!(:tracker => tracker, :status => status,
543 issue = Issue.generate!(:tracker => tracker, :status => status,
544 :project_id => 1, :author => user)
544 :project_id => 1, :author => user)
545 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
545 assert_equal [1, 2, 3, 5], issue.new_statuses_allowed_to(user).map(&:id)
546
546
547 issue = Issue.generate!(:tracker => tracker, :status => status,
547 issue = Issue.generate!(:tracker => tracker, :status => status,
548 :project_id => 1, :author_id => 1,
548 :project_id => 1, :author_id => 1,
549 :assigned_to => user)
549 :assigned_to => user)
550 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
550 assert_equal [1, 2, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
551
551
552 issue = Issue.generate!(:tracker => tracker, :status => status,
552 issue = Issue.generate!(:tracker => tracker, :status => status,
553 :project_id => 1, :author => user,
553 :project_id => 1, :author => user,
554 :assigned_to => user)
554 :assigned_to => user)
555 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
555 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
556
556
557 group = Group.generate!
557 group = Group.generate!
558 group.users << user
558 group.users << user
559 issue = Issue.generate!(:tracker => tracker, :status => status,
559 issue = Issue.generate!(:tracker => tracker, :status => status,
560 :project_id => 1, :author => user,
560 :project_id => 1, :author => user,
561 :assigned_to => group)
561 :assigned_to => group)
562 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
562 assert_equal [1, 2, 3, 4, 5], issue.new_statuses_allowed_to(user).map(&:id)
563 end
563 end
564
564
565 def test_new_statuses_allowed_to_should_consider_group_assignment
565 def test_new_statuses_allowed_to_should_consider_group_assignment
566 WorkflowTransition.delete_all
566 WorkflowTransition.delete_all
567 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
567 WorkflowTransition.create!(:role_id => 1, :tracker_id => 1,
568 :old_status_id => 1, :new_status_id => 4,
568 :old_status_id => 1, :new_status_id => 4,
569 :author => false, :assignee => true)
569 :author => false, :assignee => true)
570 user = User.find(2)
570 user = User.find(2)
571 group = Group.generate!
571 group = Group.generate!
572 group.users << user
572 group.users << user
573
573
574 issue = Issue.generate!(:author_id => 1, :assigned_to => group)
574 issue = Issue.generate!(:author_id => 1, :assigned_to => group)
575 assert_include 4, issue.new_statuses_allowed_to(user).map(&:id)
575 assert_include 4, issue.new_statuses_allowed_to(user).map(&:id)
576 end
576 end
577
577
578 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
578 def test_new_statuses_allowed_to_should_return_all_transitions_for_admin
579 admin = User.find(1)
579 admin = User.find(1)
580 issue = Issue.find(1)
580 issue = Issue.find(1)
581 assert !admin.member_of?(issue.project)
581 assert !admin.member_of?(issue.project)
582 expected_statuses = [issue.status] +
582 expected_statuses = [issue.status] +
583 WorkflowTransition.where(:old_status_id => issue.status_id).
583 WorkflowTransition.where(:old_status_id => issue.status_id).
584 map(&:new_status).uniq.sort
584 map(&:new_status).uniq.sort
585 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
585 assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
586 end
586 end
587
587
588 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
588 def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
589 issue = Issue.find(1).copy
589 issue = Issue.find(1).copy
590 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
590 assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
591
591
592 issue = Issue.find(2).copy
592 issue = Issue.find(2).copy
593 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
593 assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
594 end
594 end
595
595
596 def test_safe_attributes_names_should_not_include_disabled_field
596 def test_safe_attributes_names_should_not_include_disabled_field
597 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
597 tracker = Tracker.new(:core_fields => %w(assigned_to_id fixed_version_id))
598
598
599 issue = Issue.new(:tracker => tracker)
599 issue = Issue.new(:tracker => tracker)
600 assert_include 'tracker_id', issue.safe_attribute_names
600 assert_include 'tracker_id', issue.safe_attribute_names
601 assert_include 'status_id', issue.safe_attribute_names
601 assert_include 'status_id', issue.safe_attribute_names
602 assert_include 'subject', issue.safe_attribute_names
602 assert_include 'subject', issue.safe_attribute_names
603 assert_include 'description', issue.safe_attribute_names
603 assert_include 'description', issue.safe_attribute_names
604 assert_include 'custom_field_values', issue.safe_attribute_names
604 assert_include 'custom_field_values', issue.safe_attribute_names
605 assert_include 'custom_fields', issue.safe_attribute_names
605 assert_include 'custom_fields', issue.safe_attribute_names
606 assert_include 'lock_version', issue.safe_attribute_names
606 assert_include 'lock_version', issue.safe_attribute_names
607
607
608 tracker.core_fields.each do |field|
608 tracker.core_fields.each do |field|
609 assert_include field, issue.safe_attribute_names
609 assert_include field, issue.safe_attribute_names
610 end
610 end
611
611
612 tracker.disabled_core_fields.each do |field|
612 tracker.disabled_core_fields.each do |field|
613 assert_not_include field, issue.safe_attribute_names
613 assert_not_include field, issue.safe_attribute_names
614 end
614 end
615 end
615 end
616
616
617 def test_safe_attributes_should_ignore_disabled_fields
617 def test_safe_attributes_should_ignore_disabled_fields
618 tracker = Tracker.find(1)
618 tracker = Tracker.find(1)
619 tracker.core_fields = %w(assigned_to_id due_date)
619 tracker.core_fields = %w(assigned_to_id due_date)
620 tracker.save!
620 tracker.save!
621
621
622 issue = Issue.new(:tracker => tracker)
622 issue = Issue.new(:tracker => tracker)
623 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
623 issue.safe_attributes = {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}
624 assert_nil issue.start_date
624 assert_nil issue.start_date
625 assert_equal Date.parse('2012-07-14'), issue.due_date
625 assert_equal Date.parse('2012-07-14'), issue.due_date
626 end
626 end
627
627
628 def test_safe_attributes_should_accept_target_tracker_enabled_fields
628 def test_safe_attributes_should_accept_target_tracker_enabled_fields
629 source = Tracker.find(1)
629 source = Tracker.find(1)
630 source.core_fields = []
630 source.core_fields = []
631 source.save!
631 source.save!
632 target = Tracker.find(2)
632 target = Tracker.find(2)
633 target.core_fields = %w(assigned_to_id due_date)
633 target.core_fields = %w(assigned_to_id due_date)
634 target.save!
634 target.save!
635
635
636 issue = Issue.new(:tracker => source)
636 issue = Issue.new(:tracker => source)
637 issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
637 issue.safe_attributes = {'tracker_id' => 2, 'due_date' => '2012-07-14'}
638 assert_equal target, issue.tracker
638 assert_equal target, issue.tracker
639 assert_equal Date.parse('2012-07-14'), issue.due_date
639 assert_equal Date.parse('2012-07-14'), issue.due_date
640 end
640 end
641
641
642 def test_safe_attributes_should_not_include_readonly_fields
642 def test_safe_attributes_should_not_include_readonly_fields
643 WorkflowPermission.delete_all
643 WorkflowPermission.delete_all
644 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
644 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
645 :role_id => 1, :field_name => 'due_date',
645 :role_id => 1, :field_name => 'due_date',
646 :rule => 'readonly')
646 :rule => 'readonly')
647 user = User.find(2)
647 user = User.find(2)
648
648
649 issue = Issue.new(:project_id => 1, :tracker_id => 1)
649 issue = Issue.new(:project_id => 1, :tracker_id => 1)
650 assert_equal %w(due_date), issue.read_only_attribute_names(user)
650 assert_equal %w(due_date), issue.read_only_attribute_names(user)
651 assert_not_include 'due_date', issue.safe_attribute_names(user)
651 assert_not_include 'due_date', issue.safe_attribute_names(user)
652
652
653 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
653 issue.send :safe_attributes=, {'start_date' => '2012-07-14', 'due_date' => '2012-07-14'}, user
654 assert_equal Date.parse('2012-07-14'), issue.start_date
654 assert_equal Date.parse('2012-07-14'), issue.start_date
655 assert_nil issue.due_date
655 assert_nil issue.due_date
656 end
656 end
657
657
658 def test_safe_attributes_should_not_include_readonly_custom_fields
658 def test_safe_attributes_should_not_include_readonly_custom_fields
659 cf1 = IssueCustomField.create!(:name => 'Writable field',
659 cf1 = IssueCustomField.create!(:name => 'Writable field',
660 :field_format => 'string',
660 :field_format => 'string',
661 :is_for_all => true, :tracker_ids => [1])
661 :is_for_all => true, :tracker_ids => [1])
662 cf2 = IssueCustomField.create!(:name => 'Readonly field',
662 cf2 = IssueCustomField.create!(:name => 'Readonly field',
663 :field_format => 'string',
663 :field_format => 'string',
664 :is_for_all => true, :tracker_ids => [1])
664 :is_for_all => true, :tracker_ids => [1])
665 WorkflowPermission.delete_all
665 WorkflowPermission.delete_all
666 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
666 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
667 :role_id => 1, :field_name => cf2.id.to_s,
667 :role_id => 1, :field_name => cf2.id.to_s,
668 :rule => 'readonly')
668 :rule => 'readonly')
669 user = User.find(2)
669 user = User.find(2)
670 issue = Issue.new(:project_id => 1, :tracker_id => 1)
670 issue = Issue.new(:project_id => 1, :tracker_id => 1)
671 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
671 assert_equal [cf2.id.to_s], issue.read_only_attribute_names(user)
672 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
672 assert_not_include cf2.id.to_s, issue.safe_attribute_names(user)
673
673
674 issue.send :safe_attributes=, {'custom_field_values' => {
674 issue.send :safe_attributes=, {'custom_field_values' => {
675 cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
675 cf1.id.to_s => 'value1', cf2.id.to_s => 'value2'
676 }}, user
676 }}, user
677 assert_equal 'value1', issue.custom_field_value(cf1)
677 assert_equal 'value1', issue.custom_field_value(cf1)
678 assert_nil issue.custom_field_value(cf2)
678 assert_nil issue.custom_field_value(cf2)
679
679
680 issue.send :safe_attributes=, {'custom_fields' => [
680 issue.send :safe_attributes=, {'custom_fields' => [
681 {'id' => cf1.id.to_s, 'value' => 'valuea'},
681 {'id' => cf1.id.to_s, 'value' => 'valuea'},
682 {'id' => cf2.id.to_s, 'value' => 'valueb'}
682 {'id' => cf2.id.to_s, 'value' => 'valueb'}
683 ]}, user
683 ]}, user
684 assert_equal 'valuea', issue.custom_field_value(cf1)
684 assert_equal 'valuea', issue.custom_field_value(cf1)
685 assert_nil issue.custom_field_value(cf2)
685 assert_nil issue.custom_field_value(cf2)
686 end
686 end
687
687
688 def test_editable_custom_field_values_should_return_non_readonly_custom_values
688 def test_editable_custom_field_values_should_return_non_readonly_custom_values
689 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string',
689 cf1 = IssueCustomField.create!(:name => 'Writable field', :field_format => 'string',
690 :is_for_all => true, :tracker_ids => [1, 2])
690 :is_for_all => true, :tracker_ids => [1, 2])
691 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string',
691 cf2 = IssueCustomField.create!(:name => 'Readonly field', :field_format => 'string',
692 :is_for_all => true, :tracker_ids => [1, 2])
692 :is_for_all => true, :tracker_ids => [1, 2])
693 WorkflowPermission.delete_all
693 WorkflowPermission.delete_all
694 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1,
694 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1, :role_id => 1,
695 :field_name => cf2.id.to_s, :rule => 'readonly')
695 :field_name => cf2.id.to_s, :rule => 'readonly')
696 user = User.find(2)
696 user = User.find(2)
697
697
698 issue = Issue.new(:project_id => 1, :tracker_id => 1)
698 issue = Issue.new(:project_id => 1, :tracker_id => 1)
699 values = issue.editable_custom_field_values(user)
699 values = issue.editable_custom_field_values(user)
700 assert values.detect {|value| value.custom_field == cf1}
700 assert values.detect {|value| value.custom_field == cf1}
701 assert_nil values.detect {|value| value.custom_field == cf2}
701 assert_nil values.detect {|value| value.custom_field == cf2}
702
702
703 issue.tracker_id = 2
703 issue.tracker_id = 2
704 values = issue.editable_custom_field_values(user)
704 values = issue.editable_custom_field_values(user)
705 assert values.detect {|value| value.custom_field == cf1}
705 assert values.detect {|value| value.custom_field == cf1}
706 assert values.detect {|value| value.custom_field == cf2}
706 assert values.detect {|value| value.custom_field == cf2}
707 end
707 end
708
708
709 def test_safe_attributes_should_accept_target_tracker_writable_fields
709 def test_safe_attributes_should_accept_target_tracker_writable_fields
710 WorkflowPermission.delete_all
710 WorkflowPermission.delete_all
711 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
711 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
712 :role_id => 1, :field_name => 'due_date',
712 :role_id => 1, :field_name => 'due_date',
713 :rule => 'readonly')
713 :rule => 'readonly')
714 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
714 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
715 :role_id => 1, :field_name => 'start_date',
715 :role_id => 1, :field_name => 'start_date',
716 :rule => 'readonly')
716 :rule => 'readonly')
717 user = User.find(2)
717 user = User.find(2)
718
718
719 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
719 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
720
720
721 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
721 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
722 'due_date' => '2012-07-14'}, user
722 'due_date' => '2012-07-14'}, user
723 assert_equal Date.parse('2012-07-12'), issue.start_date
723 assert_equal Date.parse('2012-07-12'), issue.start_date
724 assert_nil issue.due_date
724 assert_nil issue.due_date
725
725
726 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
726 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
727 'due_date' => '2012-07-16',
727 'due_date' => '2012-07-16',
728 'tracker_id' => 2}, user
728 'tracker_id' => 2}, user
729 assert_equal Date.parse('2012-07-12'), issue.start_date
729 assert_equal Date.parse('2012-07-12'), issue.start_date
730 assert_equal Date.parse('2012-07-16'), issue.due_date
730 assert_equal Date.parse('2012-07-16'), issue.due_date
731 end
731 end
732
732
733 def test_safe_attributes_should_accept_target_status_writable_fields
733 def test_safe_attributes_should_accept_target_status_writable_fields
734 WorkflowPermission.delete_all
734 WorkflowPermission.delete_all
735 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
735 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
736 :role_id => 1, :field_name => 'due_date',
736 :role_id => 1, :field_name => 'due_date',
737 :rule => 'readonly')
737 :rule => 'readonly')
738 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1,
738 WorkflowPermission.create!(:old_status_id => 2, :tracker_id => 1,
739 :role_id => 1, :field_name => 'start_date',
739 :role_id => 1, :field_name => 'start_date',
740 :rule => 'readonly')
740 :rule => 'readonly')
741 user = User.find(2)
741 user = User.find(2)
742
742
743 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
743 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
744
744
745 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
745 issue.send :safe_attributes=, {'start_date' => '2012-07-12',
746 'due_date' => '2012-07-14'},
746 'due_date' => '2012-07-14'},
747 user
747 user
748 assert_equal Date.parse('2012-07-12'), issue.start_date
748 assert_equal Date.parse('2012-07-12'), issue.start_date
749 assert_nil issue.due_date
749 assert_nil issue.due_date
750
750
751 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
751 issue.send :safe_attributes=, {'start_date' => '2012-07-15',
752 'due_date' => '2012-07-16',
752 'due_date' => '2012-07-16',
753 'status_id' => 2},
753 'status_id' => 2},
754 user
754 user
755 assert_equal Date.parse('2012-07-12'), issue.start_date
755 assert_equal Date.parse('2012-07-12'), issue.start_date
756 assert_equal Date.parse('2012-07-16'), issue.due_date
756 assert_equal Date.parse('2012-07-16'), issue.due_date
757 end
757 end
758
758
759 def test_required_attributes_should_be_validated
759 def test_required_attributes_should_be_validated
760 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string',
760 cf = IssueCustomField.create!(:name => 'Foo', :field_format => 'string',
761 :is_for_all => true, :tracker_ids => [1, 2])
761 :is_for_all => true, :tracker_ids => [1, 2])
762
762
763 WorkflowPermission.delete_all
763 WorkflowPermission.delete_all
764 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
764 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
765 :role_id => 1, :field_name => 'due_date',
765 :role_id => 1, :field_name => 'due_date',
766 :rule => 'required')
766 :rule => 'required')
767 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
767 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
768 :role_id => 1, :field_name => 'category_id',
768 :role_id => 1, :field_name => 'category_id',
769 :rule => 'required')
769 :rule => 'required')
770 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
770 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
771 :role_id => 1, :field_name => cf.id.to_s,
771 :role_id => 1, :field_name => cf.id.to_s,
772 :rule => 'required')
772 :rule => 'required')
773
773
774 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
774 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
775 :role_id => 1, :field_name => 'start_date',
775 :role_id => 1, :field_name => 'start_date',
776 :rule => 'required')
776 :rule => 'required')
777 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
777 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 2,
778 :role_id => 1, :field_name => cf.id.to_s,
778 :role_id => 1, :field_name => cf.id.to_s,
779 :rule => 'required')
779 :rule => 'required')
780 user = User.find(2)
780 user = User.find(2)
781
781
782 issue = Issue.new(:project_id => 1, :tracker_id => 1,
782 issue = Issue.new(:project_id => 1, :tracker_id => 1,
783 :status_id => 1, :subject => 'Required fields',
783 :status_id => 1, :subject => 'Required fields',
784 :author => user)
784 :author => user)
785 assert_equal [cf.id.to_s, "category_id", "due_date"],
785 assert_equal [cf.id.to_s, "category_id", "due_date"],
786 issue.required_attribute_names(user).sort
786 issue.required_attribute_names(user).sort
787 assert !issue.save, "Issue was saved"
787 assert !issue.save, "Issue was saved"
788 assert_equal ["Category can't be blank", "Due date can't be blank", "Foo can't be blank"],
788 assert_equal ["Category can't be blank", "Due date can't be blank", "Foo can't be blank"],
789 issue.errors.full_messages.sort
789 issue.errors.full_messages.sort
790
790
791 issue.tracker_id = 2
791 issue.tracker_id = 2
792 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
792 assert_equal [cf.id.to_s, "start_date"], issue.required_attribute_names(user).sort
793 assert !issue.save, "Issue was saved"
793 assert !issue.save, "Issue was saved"
794 assert_equal ["Foo can't be blank", "Start date can't be blank"],
794 assert_equal ["Foo can't be blank", "Start date can't be blank"],
795 issue.errors.full_messages.sort
795 issue.errors.full_messages.sort
796
796
797 issue.start_date = Date.today
797 issue.start_date = Date.today
798 issue.custom_field_values = {cf.id.to_s => 'bar'}
798 issue.custom_field_values = {cf.id.to_s => 'bar'}
799 assert issue.save
799 assert issue.save
800 end
800 end
801
801
802 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
802 def test_required_attribute_names_for_multiple_roles_should_intersect_rules
803 WorkflowPermission.delete_all
803 WorkflowPermission.delete_all
804 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
804 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
805 :role_id => 1, :field_name => 'due_date',
805 :role_id => 1, :field_name => 'due_date',
806 :rule => 'required')
806 :rule => 'required')
807 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
807 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
808 :role_id => 1, :field_name => 'start_date',
808 :role_id => 1, :field_name => 'start_date',
809 :rule => 'required')
809 :rule => 'required')
810 user = User.find(2)
810 user = User.find(2)
811 member = Member.find(1)
811 member = Member.find(1)
812 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
812 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
813
813
814 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
814 assert_equal %w(due_date start_date), issue.required_attribute_names(user).sort
815
815
816 member.role_ids = [1, 2]
816 member.role_ids = [1, 2]
817 member.save!
817 member.save!
818 assert_equal [], issue.required_attribute_names(user.reload)
818 assert_equal [], issue.required_attribute_names(user.reload)
819
819
820 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
820 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
821 :role_id => 2, :field_name => 'due_date',
821 :role_id => 2, :field_name => 'due_date',
822 :rule => 'required')
822 :rule => 'required')
823 assert_equal %w(due_date), issue.required_attribute_names(user)
823 assert_equal %w(due_date), issue.required_attribute_names(user)
824
824
825 member.role_ids = [1, 2, 3]
825 member.role_ids = [1, 2, 3]
826 member.save!
826 member.save!
827 assert_equal [], issue.required_attribute_names(user.reload)
827 assert_equal [], issue.required_attribute_names(user.reload)
828
828
829 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
829 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
830 :role_id => 2, :field_name => 'due_date',
830 :role_id => 2, :field_name => 'due_date',
831 :rule => 'readonly')
831 :rule => 'readonly')
832 # required + readonly => required
832 # required + readonly => required
833 assert_equal %w(due_date), issue.required_attribute_names(user)
833 assert_equal %w(due_date), issue.required_attribute_names(user)
834 end
834 end
835
835
836 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
836 def test_read_only_attribute_names_for_multiple_roles_should_intersect_rules
837 WorkflowPermission.delete_all
837 WorkflowPermission.delete_all
838 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
838 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
839 :role_id => 1, :field_name => 'due_date',
839 :role_id => 1, :field_name => 'due_date',
840 :rule => 'readonly')
840 :rule => 'readonly')
841 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
841 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
842 :role_id => 1, :field_name => 'start_date',
842 :role_id => 1, :field_name => 'start_date',
843 :rule => 'readonly')
843 :rule => 'readonly')
844 user = User.find(2)
844 user = User.find(2)
845 member = Member.find(1)
845 member = Member.find(1)
846 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
846 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1)
847
847
848 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
848 assert_equal %w(due_date start_date), issue.read_only_attribute_names(user).sort
849
849
850 member.role_ids = [1, 2]
850 member.role_ids = [1, 2]
851 member.save!
851 member.save!
852 assert_equal [], issue.read_only_attribute_names(user.reload)
852 assert_equal [], issue.read_only_attribute_names(user.reload)
853
853
854 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
854 WorkflowPermission.create!(:old_status_id => 1, :tracker_id => 1,
855 :role_id => 2, :field_name => 'due_date',
855 :role_id => 2, :field_name => 'due_date',
856 :rule => 'readonly')
856 :rule => 'readonly')
857 assert_equal %w(due_date), issue.read_only_attribute_names(user)
857 assert_equal %w(due_date), issue.read_only_attribute_names(user)
858 end
858 end
859
859
860 def test_copy
860 def test_copy
861 issue = Issue.new.copy_from(1)
861 issue = Issue.new.copy_from(1)
862 assert issue.copy?
862 assert issue.copy?
863 assert issue.save
863 assert issue.save
864 issue.reload
864 issue.reload
865 orig = Issue.find(1)
865 orig = Issue.find(1)
866 assert_equal orig.subject, issue.subject
866 assert_equal orig.subject, issue.subject
867 assert_equal orig.tracker, issue.tracker
867 assert_equal orig.tracker, issue.tracker
868 assert_equal "125", issue.custom_value_for(2).value
868 assert_equal "125", issue.custom_value_for(2).value
869 end
869 end
870
870
871 def test_copy_should_copy_status
871 def test_copy_should_copy_status
872 orig = Issue.find(8)
872 orig = Issue.find(8)
873 assert orig.status != IssueStatus.default
873 assert orig.status != IssueStatus.default
874
874
875 issue = Issue.new.copy_from(orig)
875 issue = Issue.new.copy_from(orig)
876 assert issue.save
876 assert issue.save
877 issue.reload
877 issue.reload
878 assert_equal orig.status, issue.status
878 assert_equal orig.status, issue.status
879 end
879 end
880
880
881 def test_copy_should_add_relation_with_copied_issue
881 def test_copy_should_add_relation_with_copied_issue
882 copied = Issue.find(1)
882 copied = Issue.find(1)
883 issue = Issue.new.copy_from(copied)
883 issue = Issue.new.copy_from(copied)
884 assert issue.save
884 assert issue.save
885 issue.reload
885 issue.reload
886
886
887 assert_equal 1, issue.relations.size
887 assert_equal 1, issue.relations.size
888 relation = issue.relations.first
888 relation = issue.relations.first
889 assert_equal 'copied_to', relation.relation_type
889 assert_equal 'copied_to', relation.relation_type
890 assert_equal copied, relation.issue_from
890 assert_equal copied, relation.issue_from
891 assert_equal issue, relation.issue_to
891 assert_equal issue, relation.issue_to
892 end
892 end
893
893
894 def test_copy_should_copy_subtasks
894 def test_copy_should_copy_subtasks
895 issue = Issue.generate_with_descendants!
895 issue = Issue.generate_with_descendants!
896
896
897 copy = issue.reload.copy
897 copy = issue.reload.copy
898 copy.author = User.find(7)
898 copy.author = User.find(7)
899 assert_difference 'Issue.count', 1+issue.descendants.count do
899 assert_difference 'Issue.count', 1+issue.descendants.count do
900 assert copy.save
900 assert copy.save
901 end
901 end
902 copy.reload
902 copy.reload
903 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
903 assert_equal %w(Child1 Child2), copy.children.map(&:subject).sort
904 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
904 child_copy = copy.children.detect {|c| c.subject == 'Child1'}
905 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
905 assert_equal %w(Child11), child_copy.children.map(&:subject).sort
906 assert_equal copy.author, child_copy.author
906 assert_equal copy.author, child_copy.author
907 end
907 end
908
908
909 def test_copy_as_a_child_of_copied_issue_should_not_copy_itself
909 def test_copy_as_a_child_of_copied_issue_should_not_copy_itself
910 parent = Issue.generate!
910 parent = Issue.generate!
911 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
911 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
912 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
912 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
913
913
914 copy = parent.reload.copy
914 copy = parent.reload.copy
915 copy.parent_issue_id = parent.id
915 copy.parent_issue_id = parent.id
916 copy.author = User.find(7)
916 copy.author = User.find(7)
917 assert_difference 'Issue.count', 3 do
917 assert_difference 'Issue.count', 3 do
918 assert copy.save
918 assert copy.save
919 end
919 end
920 parent.reload
920 parent.reload
921 copy.reload
921 copy.reload
922 assert_equal parent, copy.parent
922 assert_equal parent, copy.parent
923 assert_equal 3, parent.children.count
923 assert_equal 3, parent.children.count
924 assert_equal 5, parent.descendants.count
924 assert_equal 5, parent.descendants.count
925 assert_equal 2, copy.children.count
925 assert_equal 2, copy.children.count
926 assert_equal 2, copy.descendants.count
926 assert_equal 2, copy.descendants.count
927 end
927 end
928
928
929 def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself
929 def test_copy_as_a_descendant_of_copied_issue_should_not_copy_itself
930 parent = Issue.generate!
930 parent = Issue.generate!
931 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
931 child1 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 1')
932 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
932 child2 = Issue.generate!(:parent_issue_id => parent.id, :subject => 'Child 2')
933
933
934 copy = parent.reload.copy
934 copy = parent.reload.copy
935 copy.parent_issue_id = child1.id
935 copy.parent_issue_id = child1.id
936 copy.author = User.find(7)
936 copy.author = User.find(7)
937 assert_difference 'Issue.count', 3 do
937 assert_difference 'Issue.count', 3 do
938 assert copy.save
938 assert copy.save
939 end
939 end
940 parent.reload
940 parent.reload
941 child1.reload
941 child1.reload
942 copy.reload
942 copy.reload
943 assert_equal child1, copy.parent
943 assert_equal child1, copy.parent
944 assert_equal 2, parent.children.count
944 assert_equal 2, parent.children.count
945 assert_equal 5, parent.descendants.count
945 assert_equal 5, parent.descendants.count
946 assert_equal 1, child1.children.count
946 assert_equal 1, child1.children.count
947 assert_equal 3, child1.descendants.count
947 assert_equal 3, child1.descendants.count
948 assert_equal 2, copy.children.count
948 assert_equal 2, copy.children.count
949 assert_equal 2, copy.descendants.count
949 assert_equal 2, copy.descendants.count
950 end
950 end
951
951
952 def test_copy_should_copy_subtasks_to_target_project
952 def test_copy_should_copy_subtasks_to_target_project
953 issue = Issue.generate_with_descendants!
953 issue = Issue.generate_with_descendants!
954
954
955 copy = issue.copy(:project_id => 3)
955 copy = issue.copy(:project_id => 3)
956 assert_difference 'Issue.count', 1+issue.descendants.count do
956 assert_difference 'Issue.count', 1+issue.descendants.count do
957 assert copy.save
957 assert copy.save
958 end
958 end
959 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
959 assert_equal [3], copy.reload.descendants.map(&:project_id).uniq
960 end
960 end
961
961
962 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
962 def test_copy_should_not_copy_subtasks_twice_when_saving_twice
963 issue = Issue.generate_with_descendants!
963 issue = Issue.generate_with_descendants!
964
964
965 copy = issue.reload.copy
965 copy = issue.reload.copy
966 assert_difference 'Issue.count', 1+issue.descendants.count do
966 assert_difference 'Issue.count', 1+issue.descendants.count do
967 assert copy.save
967 assert copy.save
968 assert copy.save
968 assert copy.save
969 end
969 end
970 end
970 end
971
971
972 def test_should_not_call_after_project_change_on_creation
972 def test_should_not_call_after_project_change_on_creation
973 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
973 issue = Issue.new(:project_id => 1, :tracker_id => 1, :status_id => 1,
974 :subject => 'Test', :author_id => 1)
974 :subject => 'Test', :author_id => 1)
975 issue.expects(:after_project_change).never
975 issue.expects(:after_project_change).never
976 issue.save!
976 issue.save!
977 end
977 end
978
978
979 def test_should_not_call_after_project_change_on_update
979 def test_should_not_call_after_project_change_on_update
980 issue = Issue.find(1)
980 issue = Issue.find(1)
981 issue.project = Project.find(1)
981 issue.project = Project.find(1)
982 issue.subject = 'No project change'
982 issue.subject = 'No project change'
983 issue.expects(:after_project_change).never
983 issue.expects(:after_project_change).never
984 issue.save!
984 issue.save!
985 end
985 end
986
986
987 def test_should_call_after_project_change_on_project_change
987 def test_should_call_after_project_change_on_project_change
988 issue = Issue.find(1)
988 issue = Issue.find(1)
989 issue.project = Project.find(2)
989 issue.project = Project.find(2)
990 issue.expects(:after_project_change).once
990 issue.expects(:after_project_change).once
991 issue.save!
991 issue.save!
992 end
992 end
993
993
994 def test_adding_journal_should_update_timestamp
994 def test_adding_journal_should_update_timestamp
995 issue = Issue.find(1)
995 issue = Issue.find(1)
996 updated_on_was = issue.updated_on
996 updated_on_was = issue.updated_on
997
997
998 issue.init_journal(User.first, "Adding notes")
998 issue.init_journal(User.first, "Adding notes")
999 assert_difference 'Journal.count' do
999 assert_difference 'Journal.count' do
1000 assert issue.save
1000 assert issue.save
1001 end
1001 end
1002 issue.reload
1002 issue.reload
1003
1003
1004 assert_not_equal updated_on_was, issue.updated_on
1004 assert_not_equal updated_on_was, issue.updated_on
1005 end
1005 end
1006
1006
1007 def test_should_close_duplicates
1007 def test_should_close_duplicates
1008 # Create 3 issues
1008 # Create 3 issues
1009 issue1 = Issue.generate!
1009 issue1 = Issue.generate!
1010 issue2 = Issue.generate!
1010 issue2 = Issue.generate!
1011 issue3 = Issue.generate!
1011 issue3 = Issue.generate!
1012
1012
1013 # 2 is a dupe of 1
1013 # 2 is a dupe of 1
1014 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1,
1014 IssueRelation.create!(:issue_from => issue2, :issue_to => issue1,
1015 :relation_type => IssueRelation::TYPE_DUPLICATES)
1015 :relation_type => IssueRelation::TYPE_DUPLICATES)
1016 # And 3 is a dupe of 2
1016 # And 3 is a dupe of 2
1017 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1017 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1018 :relation_type => IssueRelation::TYPE_DUPLICATES)
1018 :relation_type => IssueRelation::TYPE_DUPLICATES)
1019 # And 3 is a dupe of 1 (circular duplicates)
1019 # And 3 is a dupe of 1 (circular duplicates)
1020 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1,
1020 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1,
1021 :relation_type => IssueRelation::TYPE_DUPLICATES)
1021 :relation_type => IssueRelation::TYPE_DUPLICATES)
1022
1022
1023 assert issue1.reload.duplicates.include?(issue2)
1023 assert issue1.reload.duplicates.include?(issue2)
1024
1024
1025 # Closing issue 1
1025 # Closing issue 1
1026 issue1.init_journal(User.first, "Closing issue1")
1026 issue1.init_journal(User.first, "Closing issue1")
1027 issue1.status = IssueStatus.where(:is_closed => true).first
1027 issue1.status = IssueStatus.where(:is_closed => true).first
1028 assert issue1.save
1028 assert issue1.save
1029 # 2 and 3 should be also closed
1029 # 2 and 3 should be also closed
1030 assert issue2.reload.closed?
1030 assert issue2.reload.closed?
1031 assert issue3.reload.closed?
1031 assert issue3.reload.closed?
1032 end
1032 end
1033
1033
1034 def test_should_not_close_duplicated_issue
1034 def test_should_not_close_duplicated_issue
1035 issue1 = Issue.generate!
1035 issue1 = Issue.generate!
1036 issue2 = Issue.generate!
1036 issue2 = Issue.generate!
1037
1037
1038 # 2 is a dupe of 1
1038 # 2 is a dupe of 1
1039 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1039 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1040 :relation_type => IssueRelation::TYPE_DUPLICATES)
1040 :relation_type => IssueRelation::TYPE_DUPLICATES)
1041 # 2 is a dup of 1 but 1 is not a duplicate of 2
1041 # 2 is a dup of 1 but 1 is not a duplicate of 2
1042 assert !issue2.reload.duplicates.include?(issue1)
1042 assert !issue2.reload.duplicates.include?(issue1)
1043
1043
1044 # Closing issue 2
1044 # Closing issue 2
1045 issue2.init_journal(User.first, "Closing issue2")
1045 issue2.init_journal(User.first, "Closing issue2")
1046 issue2.status = IssueStatus.where(:is_closed => true).first
1046 issue2.status = IssueStatus.where(:is_closed => true).first
1047 assert issue2.save
1047 assert issue2.save
1048 # 1 should not be also closed
1048 # 1 should not be also closed
1049 assert !issue1.reload.closed?
1049 assert !issue1.reload.closed?
1050 end
1050 end
1051
1051
1052 def test_assignable_versions
1052 def test_assignable_versions
1053 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1053 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1054 :status_id => 1, :fixed_version_id => 1,
1054 :status_id => 1, :fixed_version_id => 1,
1055 :subject => 'New issue')
1055 :subject => 'New issue')
1056 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1056 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1057 end
1057 end
1058
1058
1059 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
1059 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
1060 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1060 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1061 :status_id => 1, :fixed_version_id => 1,
1061 :status_id => 1, :fixed_version_id => 1,
1062 :subject => 'New issue')
1062 :subject => 'New issue')
1063 assert !issue.save
1063 assert !issue.save
1064 assert_not_equal [], issue.errors[:fixed_version_id]
1064 assert_not_equal [], issue.errors[:fixed_version_id]
1065 end
1065 end
1066
1066
1067 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
1067 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
1068 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1068 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1069 :status_id => 1, :fixed_version_id => 2,
1069 :status_id => 1, :fixed_version_id => 2,
1070 :subject => 'New issue')
1070 :subject => 'New issue')
1071 assert !issue.save
1071 assert !issue.save
1072 assert_not_equal [], issue.errors[:fixed_version_id]
1072 assert_not_equal [], issue.errors[:fixed_version_id]
1073 end
1073 end
1074
1074
1075 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
1075 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
1076 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1076 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1077 :status_id => 1, :fixed_version_id => 3,
1077 :status_id => 1, :fixed_version_id => 3,
1078 :subject => 'New issue')
1078 :subject => 'New issue')
1079 assert issue.save
1079 assert issue.save
1080 end
1080 end
1081
1081
1082 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1082 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1083 issue = Issue.find(11)
1083 issue = Issue.find(11)
1084 assert_equal 'closed', issue.fixed_version.status
1084 assert_equal 'closed', issue.fixed_version.status
1085 issue.subject = 'Subject changed'
1085 issue.subject = 'Subject changed'
1086 assert issue.save
1086 assert issue.save
1087 end
1087 end
1088
1088
1089 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1089 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1090 issue = Issue.find(11)
1090 issue = Issue.find(11)
1091 issue.status_id = 1
1091 issue.status_id = 1
1092 assert !issue.save
1092 assert !issue.save
1093 assert_not_equal [], issue.errors[:base]
1093 assert_not_equal [], issue.errors[:base]
1094 end
1094 end
1095
1095
1096 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1096 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1097 issue = Issue.find(11)
1097 issue = Issue.find(11)
1098 issue.status_id = 1
1098 issue.status_id = 1
1099 issue.fixed_version_id = 3
1099 issue.fixed_version_id = 3
1100 assert issue.save
1100 assert issue.save
1101 end
1101 end
1102
1102
1103 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1103 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1104 issue = Issue.find(12)
1104 issue = Issue.find(12)
1105 assert_equal 'locked', issue.fixed_version.status
1105 assert_equal 'locked', issue.fixed_version.status
1106 issue.status_id = 1
1106 issue.status_id = 1
1107 assert issue.save
1107 assert issue.save
1108 end
1108 end
1109
1109
1110 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1110 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1111 issue = Issue.find(2)
1111 issue = Issue.find(2)
1112 assert_equal 2, issue.fixed_version_id
1112 assert_equal 2, issue.fixed_version_id
1113 issue.project_id = 3
1113 issue.project_id = 3
1114 assert_nil issue.fixed_version_id
1114 assert_nil issue.fixed_version_id
1115 issue.fixed_version_id = 2
1115 issue.fixed_version_id = 2
1116 assert !issue.save
1116 assert !issue.save
1117 assert_include 'Target version is not included in the list', issue.errors.full_messages
1117 assert_include 'Target version is not included in the list', issue.errors.full_messages
1118 end
1118 end
1119
1119
1120 def test_should_keep_shared_version_when_changing_project
1120 def test_should_keep_shared_version_when_changing_project
1121 Version.find(2).update_attribute :sharing, 'tree'
1121 Version.find(2).update_attribute :sharing, 'tree'
1122
1122
1123 issue = Issue.find(2)
1123 issue = Issue.find(2)
1124 assert_equal 2, issue.fixed_version_id
1124 assert_equal 2, issue.fixed_version_id
1125 issue.project_id = 3
1125 issue.project_id = 3
1126 assert_equal 2, issue.fixed_version_id
1126 assert_equal 2, issue.fixed_version_id
1127 assert issue.save
1127 assert issue.save
1128 end
1128 end
1129
1129
1130 def test_allowed_target_projects_on_move_should_include_projects_with_issue_tracking_enabled
1130 def test_allowed_target_projects_on_move_should_include_projects_with_issue_tracking_enabled
1131 assert_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
1131 assert_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
1132 end
1132 end
1133
1133
1134 def test_allowed_target_projects_on_move_should_not_include_projects_with_issue_tracking_disabled
1134 def test_allowed_target_projects_on_move_should_not_include_projects_with_issue_tracking_disabled
1135 Project.find(2).disable_module! :issue_tracking
1135 Project.find(2).disable_module! :issue_tracking
1136 assert_not_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
1136 assert_not_include Project.find(2), Issue.allowed_target_projects_on_move(User.find(2))
1137 end
1137 end
1138
1138
1139 def test_move_to_another_project_with_same_category
1139 def test_move_to_another_project_with_same_category
1140 issue = Issue.find(1)
1140 issue = Issue.find(1)
1141 issue.project = Project.find(2)
1141 issue.project = Project.find(2)
1142 assert issue.save
1142 assert issue.save
1143 issue.reload
1143 issue.reload
1144 assert_equal 2, issue.project_id
1144 assert_equal 2, issue.project_id
1145 # Category changes
1145 # Category changes
1146 assert_equal 4, issue.category_id
1146 assert_equal 4, issue.category_id
1147 # Make sure time entries were move to the target project
1147 # Make sure time entries were move to the target project
1148 assert_equal 2, issue.time_entries.first.project_id
1148 assert_equal 2, issue.time_entries.first.project_id
1149 end
1149 end
1150
1150
1151 def test_move_to_another_project_without_same_category
1151 def test_move_to_another_project_without_same_category
1152 issue = Issue.find(2)
1152 issue = Issue.find(2)
1153 issue.project = Project.find(2)
1153 issue.project = Project.find(2)
1154 assert issue.save
1154 assert issue.save
1155 issue.reload
1155 issue.reload
1156 assert_equal 2, issue.project_id
1156 assert_equal 2, issue.project_id
1157 # Category cleared
1157 # Category cleared
1158 assert_nil issue.category_id
1158 assert_nil issue.category_id
1159 end
1159 end
1160
1160
1161 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1161 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1162 issue = Issue.find(1)
1162 issue = Issue.find(1)
1163 issue.update_attribute(:fixed_version_id, 1)
1163 issue.update_attribute(:fixed_version_id, 1)
1164 issue.project = Project.find(2)
1164 issue.project = Project.find(2)
1165 assert issue.save
1165 assert issue.save
1166 issue.reload
1166 issue.reload
1167 assert_equal 2, issue.project_id
1167 assert_equal 2, issue.project_id
1168 # Cleared fixed_version
1168 # Cleared fixed_version
1169 assert_equal nil, issue.fixed_version
1169 assert_equal nil, issue.fixed_version
1170 end
1170 end
1171
1171
1172 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1172 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1173 issue = Issue.find(1)
1173 issue = Issue.find(1)
1174 issue.update_attribute(:fixed_version_id, 4)
1174 issue.update_attribute(:fixed_version_id, 4)
1175 issue.project = Project.find(5)
1175 issue.project = Project.find(5)
1176 assert issue.save
1176 assert issue.save
1177 issue.reload
1177 issue.reload
1178 assert_equal 5, issue.project_id
1178 assert_equal 5, issue.project_id
1179 # Keep fixed_version
1179 # Keep fixed_version
1180 assert_equal 4, issue.fixed_version_id
1180 assert_equal 4, issue.fixed_version_id
1181 end
1181 end
1182
1182
1183 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1183 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1184 issue = Issue.find(1)
1184 issue = Issue.find(1)
1185 issue.update_attribute(:fixed_version_id, 1)
1185 issue.update_attribute(:fixed_version_id, 1)
1186 issue.project = Project.find(5)
1186 issue.project = Project.find(5)
1187 assert issue.save
1187 assert issue.save
1188 issue.reload
1188 issue.reload
1189 assert_equal 5, issue.project_id
1189 assert_equal 5, issue.project_id
1190 # Cleared fixed_version
1190 # Cleared fixed_version
1191 assert_equal nil, issue.fixed_version
1191 assert_equal nil, issue.fixed_version
1192 end
1192 end
1193
1193
1194 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1194 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1195 issue = Issue.find(1)
1195 issue = Issue.find(1)
1196 issue.update_attribute(:fixed_version_id, 7)
1196 issue.update_attribute(:fixed_version_id, 7)
1197 issue.project = Project.find(2)
1197 issue.project = Project.find(2)
1198 assert issue.save
1198 assert issue.save
1199 issue.reload
1199 issue.reload
1200 assert_equal 2, issue.project_id
1200 assert_equal 2, issue.project_id
1201 # Keep fixed_version
1201 # Keep fixed_version
1202 assert_equal 7, issue.fixed_version_id
1202 assert_equal 7, issue.fixed_version_id
1203 end
1203 end
1204
1204
1205 def test_move_to_another_project_should_keep_parent_if_valid
1205 def test_move_to_another_project_should_keep_parent_if_valid
1206 issue = Issue.find(1)
1206 issue = Issue.find(1)
1207 issue.update_attribute(:parent_issue_id, 2)
1207 issue.update_attribute(:parent_issue_id, 2)
1208 issue.project = Project.find(3)
1208 issue.project = Project.find(3)
1209 assert issue.save
1209 assert issue.save
1210 issue.reload
1210 issue.reload
1211 assert_equal 2, issue.parent_id
1211 assert_equal 2, issue.parent_id
1212 end
1212 end
1213
1213
1214 def test_move_to_another_project_should_clear_parent_if_not_valid
1214 def test_move_to_another_project_should_clear_parent_if_not_valid
1215 issue = Issue.find(1)
1215 issue = Issue.find(1)
1216 issue.update_attribute(:parent_issue_id, 2)
1216 issue.update_attribute(:parent_issue_id, 2)
1217 issue.project = Project.find(2)
1217 issue.project = Project.find(2)
1218 assert issue.save
1218 assert issue.save
1219 issue.reload
1219 issue.reload
1220 assert_nil issue.parent_id
1220 assert_nil issue.parent_id
1221 end
1221 end
1222
1222
1223 def test_move_to_another_project_with_disabled_tracker
1223 def test_move_to_another_project_with_disabled_tracker
1224 issue = Issue.find(1)
1224 issue = Issue.find(1)
1225 target = Project.find(2)
1225 target = Project.find(2)
1226 target.tracker_ids = [3]
1226 target.tracker_ids = [3]
1227 target.save
1227 target.save
1228 issue.project = target
1228 issue.project = target
1229 assert issue.save
1229 assert issue.save
1230 issue.reload
1230 issue.reload
1231 assert_equal 2, issue.project_id
1231 assert_equal 2, issue.project_id
1232 assert_equal 3, issue.tracker_id
1232 assert_equal 3, issue.tracker_id
1233 end
1233 end
1234
1234
1235 def test_copy_to_the_same_project
1235 def test_copy_to_the_same_project
1236 issue = Issue.find(1)
1236 issue = Issue.find(1)
1237 copy = issue.copy
1237 copy = issue.copy
1238 assert_difference 'Issue.count' do
1238 assert_difference 'Issue.count' do
1239 copy.save!
1239 copy.save!
1240 end
1240 end
1241 assert_kind_of Issue, copy
1241 assert_kind_of Issue, copy
1242 assert_equal issue.project, copy.project
1242 assert_equal issue.project, copy.project
1243 assert_equal "125", copy.custom_value_for(2).value
1243 assert_equal "125", copy.custom_value_for(2).value
1244 end
1244 end
1245
1245
1246 def test_copy_to_another_project_and_tracker
1246 def test_copy_to_another_project_and_tracker
1247 issue = Issue.find(1)
1247 issue = Issue.find(1)
1248 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1248 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1249 assert_difference 'Issue.count' do
1249 assert_difference 'Issue.count' do
1250 copy.save!
1250 copy.save!
1251 end
1251 end
1252 copy.reload
1252 copy.reload
1253 assert_kind_of Issue, copy
1253 assert_kind_of Issue, copy
1254 assert_equal Project.find(3), copy.project
1254 assert_equal Project.find(3), copy.project
1255 assert_equal Tracker.find(2), copy.tracker
1255 assert_equal Tracker.find(2), copy.tracker
1256 # Custom field #2 is not associated with target tracker
1256 # Custom field #2 is not associated with target tracker
1257 assert_nil copy.custom_value_for(2)
1257 assert_nil copy.custom_value_for(2)
1258 end
1258 end
1259
1259
1260 test "#copy should not create a journal" do
1260 test "#copy should not create a journal" do
1261 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1261 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1262 copy.save!
1262 copy.save!
1263 assert_equal 0, copy.reload.journals.size
1263 assert_equal 0, copy.reload.journals.size
1264 end
1264 end
1265
1265
1266 test "#copy should allow assigned_to changes" do
1266 test "#copy should allow assigned_to changes" do
1267 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1267 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1268 assert_equal 3, copy.assigned_to_id
1268 assert_equal 3, copy.assigned_to_id
1269 end
1269 end
1270
1270
1271 test "#copy should allow status changes" do
1271 test "#copy should allow status changes" do
1272 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1272 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1273 assert_equal 2, copy.status_id
1273 assert_equal 2, copy.status_id
1274 end
1274 end
1275
1275
1276 test "#copy should allow start date changes" do
1276 test "#copy should allow start date changes" do
1277 date = Date.today
1277 date = Date.today
1278 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1278 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1279 assert_equal date, copy.start_date
1279 assert_equal date, copy.start_date
1280 end
1280 end
1281
1281
1282 test "#copy should allow due date changes" do
1282 test "#copy should allow due date changes" do
1283 date = Date.today
1283 date = Date.today
1284 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1284 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1285 assert_equal date, copy.due_date
1285 assert_equal date, copy.due_date
1286 end
1286 end
1287
1287
1288 test "#copy should set current user as author" do
1288 test "#copy should set current user as author" do
1289 User.current = User.find(9)
1289 User.current = User.find(9)
1290 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1290 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1291 assert_equal User.current, copy.author
1291 assert_equal User.current, copy.author
1292 end
1292 end
1293
1293
1294 test "#copy should create a journal with notes" do
1294 test "#copy should create a journal with notes" do
1295 date = Date.today
1295 date = Date.today
1296 notes = "Notes added when copying"
1296 notes = "Notes added when copying"
1297 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1297 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1298 copy.init_journal(User.current, notes)
1298 copy.init_journal(User.current, notes)
1299 copy.save!
1299 copy.save!
1300
1300
1301 assert_equal 1, copy.journals.size
1301 assert_equal 1, copy.journals.size
1302 journal = copy.journals.first
1302 journal = copy.journals.first
1303 assert_equal 0, journal.details.size
1303 assert_equal 0, journal.details.size
1304 assert_equal notes, journal.notes
1304 assert_equal notes, journal.notes
1305 end
1305 end
1306
1306
1307 def test_valid_parent_project
1307 def test_valid_parent_project
1308 issue = Issue.find(1)
1308 issue = Issue.find(1)
1309 issue_in_same_project = Issue.find(2)
1309 issue_in_same_project = Issue.find(2)
1310 issue_in_child_project = Issue.find(5)
1310 issue_in_child_project = Issue.find(5)
1311 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1311 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1312 issue_in_other_child_project = Issue.find(6)
1312 issue_in_other_child_project = Issue.find(6)
1313 issue_in_different_tree = Issue.find(4)
1313 issue_in_different_tree = Issue.find(4)
1314
1314
1315 with_settings :cross_project_subtasks => '' do
1315 with_settings :cross_project_subtasks => '' do
1316 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1316 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1317 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1317 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1318 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1318 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1319 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1319 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1320 end
1320 end
1321
1321
1322 with_settings :cross_project_subtasks => 'system' do
1322 with_settings :cross_project_subtasks => 'system' do
1323 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1323 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1324 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1324 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1325 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1325 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1326 end
1326 end
1327
1327
1328 with_settings :cross_project_subtasks => 'tree' do
1328 with_settings :cross_project_subtasks => 'tree' do
1329 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1329 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1330 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1330 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1331 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1331 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1332 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1332 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1333
1333
1334 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1334 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1335 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1335 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1336 end
1336 end
1337
1337
1338 with_settings :cross_project_subtasks => 'descendants' do
1338 with_settings :cross_project_subtasks => 'descendants' do
1339 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1339 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1340 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1340 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1341 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1341 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1342 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1342 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1343
1343
1344 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1344 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1345 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1345 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1346 end
1346 end
1347 end
1347 end
1348
1348
1349 def test_recipients_should_include_previous_assignee
1349 def test_recipients_should_include_previous_assignee
1350 user = User.find(3)
1350 user = User.find(3)
1351 user.members.update_all ["mail_notification = ?", false]
1351 user.members.update_all ["mail_notification = ?", false]
1352 user.update_attribute :mail_notification, 'only_assigned'
1352 user.update_attribute :mail_notification, 'only_assigned'
1353
1353
1354 issue = Issue.find(2)
1354 issue = Issue.find(2)
1355 issue.assigned_to = nil
1355 issue.assigned_to = nil
1356 assert_include user.mail, issue.recipients
1356 assert_include user.mail, issue.recipients
1357 issue.save!
1357 issue.save!
1358 assert !issue.recipients.include?(user.mail)
1358 assert !issue.recipients.include?(user.mail)
1359 end
1359 end
1360
1360
1361 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1361 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1362 issue = Issue.find(12)
1362 issue = Issue.find(12)
1363 assert issue.recipients.include?(issue.author.mail)
1363 assert issue.recipients.include?(issue.author.mail)
1364 # copy the issue to a private project
1364 # copy the issue to a private project
1365 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1365 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1366 # author is not a member of project anymore
1366 # author is not a member of project anymore
1367 assert !copy.recipients.include?(copy.author.mail)
1367 assert !copy.recipients.include?(copy.author.mail)
1368 end
1368 end
1369
1369
1370 def test_recipients_should_include_the_assigned_group_members
1370 def test_recipients_should_include_the_assigned_group_members
1371 group_member = User.generate!
1371 group_member = User.generate!
1372 group = Group.generate!
1372 group = Group.generate!
1373 group.users << group_member
1373 group.users << group_member
1374
1374
1375 issue = Issue.find(12)
1375 issue = Issue.find(12)
1376 issue.assigned_to = group
1376 issue.assigned_to = group
1377 assert issue.recipients.include?(group_member.mail)
1377 assert issue.recipients.include?(group_member.mail)
1378 end
1378 end
1379
1379
1380 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1380 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1381 user = User.find(3)
1381 user = User.find(3)
1382 issue = Issue.find(9)
1382 issue = Issue.find(9)
1383 Watcher.create!(:user => user, :watchable => issue)
1383 Watcher.create!(:user => user, :watchable => issue)
1384 assert issue.watched_by?(user)
1384 assert issue.watched_by?(user)
1385 assert !issue.watcher_recipients.include?(user.mail)
1385 assert !issue.watcher_recipients.include?(user.mail)
1386 end
1386 end
1387
1387
1388 def test_issue_destroy
1388 def test_issue_destroy
1389 Issue.find(1).destroy
1389 Issue.find(1).destroy
1390 assert_nil Issue.find_by_id(1)
1390 assert_nil Issue.find_by_id(1)
1391 assert_nil TimeEntry.find_by_issue_id(1)
1391 assert_nil TimeEntry.find_by_issue_id(1)
1392 end
1392 end
1393
1393
1394 def test_destroy_should_delete_time_entries_custom_values
1394 def test_destroy_should_delete_time_entries_custom_values
1395 issue = Issue.generate!
1395 issue = Issue.generate!
1396 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1396 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1397
1397
1398 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1398 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1399 assert issue.destroy
1399 assert issue.destroy
1400 end
1400 end
1401 end
1401 end
1402
1402
1403 def test_destroying_a_deleted_issue_should_not_raise_an_error
1403 def test_destroying_a_deleted_issue_should_not_raise_an_error
1404 issue = Issue.find(1)
1404 issue = Issue.find(1)
1405 Issue.find(1).destroy
1405 Issue.find(1).destroy
1406
1406
1407 assert_nothing_raised do
1407 assert_nothing_raised do
1408 assert_no_difference 'Issue.count' do
1408 assert_no_difference 'Issue.count' do
1409 issue.destroy
1409 issue.destroy
1410 end
1410 end
1411 assert issue.destroyed?
1411 assert issue.destroyed?
1412 end
1412 end
1413 end
1413 end
1414
1414
1415 def test_destroying_a_stale_issue_should_not_raise_an_error
1415 def test_destroying_a_stale_issue_should_not_raise_an_error
1416 issue = Issue.find(1)
1416 issue = Issue.find(1)
1417 Issue.find(1).update_attribute :subject, "Updated"
1417 Issue.find(1).update_attribute :subject, "Updated"
1418
1418
1419 assert_nothing_raised do
1419 assert_nothing_raised do
1420 assert_difference 'Issue.count', -1 do
1420 assert_difference 'Issue.count', -1 do
1421 issue.destroy
1421 issue.destroy
1422 end
1422 end
1423 assert issue.destroyed?
1423 assert issue.destroyed?
1424 end
1424 end
1425 end
1425 end
1426
1426
1427 def test_blocked
1427 def test_blocked
1428 blocked_issue = Issue.find(9)
1428 blocked_issue = Issue.find(9)
1429 blocking_issue = Issue.find(10)
1429 blocking_issue = Issue.find(10)
1430
1430
1431 assert blocked_issue.blocked?
1431 assert blocked_issue.blocked?
1432 assert !blocking_issue.blocked?
1432 assert !blocking_issue.blocked?
1433 end
1433 end
1434
1434
1435 def test_blocked_issues_dont_allow_closed_statuses
1435 def test_blocked_issues_dont_allow_closed_statuses
1436 blocked_issue = Issue.find(9)
1436 blocked_issue = Issue.find(9)
1437
1437
1438 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1438 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1439 assert !allowed_statuses.empty?
1439 assert !allowed_statuses.empty?
1440 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1440 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1441 assert closed_statuses.empty?
1441 assert closed_statuses.empty?
1442 end
1442 end
1443
1443
1444 def test_unblocked_issues_allow_closed_statuses
1444 def test_unblocked_issues_allow_closed_statuses
1445 blocking_issue = Issue.find(10)
1445 blocking_issue = Issue.find(10)
1446
1446
1447 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1447 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1448 assert !allowed_statuses.empty?
1448 assert !allowed_statuses.empty?
1449 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1449 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1450 assert !closed_statuses.empty?
1450 assert !closed_statuses.empty?
1451 end
1451 end
1452
1452
1453 def test_reschedule_an_issue_without_dates
1453 def test_reschedule_an_issue_without_dates
1454 with_settings :non_working_week_days => [] do
1454 with_settings :non_working_week_days => [] do
1455 issue = Issue.new(:start_date => nil, :due_date => nil)
1455 issue = Issue.new(:start_date => nil, :due_date => nil)
1456 issue.reschedule_on '2012-10-09'.to_date
1456 issue.reschedule_on '2012-10-09'.to_date
1457 assert_equal '2012-10-09'.to_date, issue.start_date
1457 assert_equal '2012-10-09'.to_date, issue.start_date
1458 assert_equal '2012-10-09'.to_date, issue.due_date
1458 assert_equal '2012-10-09'.to_date, issue.due_date
1459 end
1459 end
1460
1460
1461 with_settings :non_working_week_days => %w(6 7) do
1461 with_settings :non_working_week_days => %w(6 7) do
1462 issue = Issue.new(:start_date => nil, :due_date => nil)
1462 issue = Issue.new(:start_date => nil, :due_date => nil)
1463 issue.reschedule_on '2012-10-09'.to_date
1463 issue.reschedule_on '2012-10-09'.to_date
1464 assert_equal '2012-10-09'.to_date, issue.start_date
1464 assert_equal '2012-10-09'.to_date, issue.start_date
1465 assert_equal '2012-10-09'.to_date, issue.due_date
1465 assert_equal '2012-10-09'.to_date, issue.due_date
1466
1466
1467 issue = Issue.new(:start_date => nil, :due_date => nil)
1467 issue = Issue.new(:start_date => nil, :due_date => nil)
1468 issue.reschedule_on '2012-10-13'.to_date
1468 issue.reschedule_on '2012-10-13'.to_date
1469 assert_equal '2012-10-15'.to_date, issue.start_date
1469 assert_equal '2012-10-15'.to_date, issue.start_date
1470 assert_equal '2012-10-15'.to_date, issue.due_date
1470 assert_equal '2012-10-15'.to_date, issue.due_date
1471 end
1471 end
1472 end
1472 end
1473
1473
1474 def test_reschedule_an_issue_with_start_date
1474 def test_reschedule_an_issue_with_start_date
1475 with_settings :non_working_week_days => [] do
1475 with_settings :non_working_week_days => [] do
1476 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1476 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1477 issue.reschedule_on '2012-10-13'.to_date
1477 issue.reschedule_on '2012-10-13'.to_date
1478 assert_equal '2012-10-13'.to_date, issue.start_date
1478 assert_equal '2012-10-13'.to_date, issue.start_date
1479 assert_equal '2012-10-13'.to_date, issue.due_date
1479 assert_equal '2012-10-13'.to_date, issue.due_date
1480 end
1480 end
1481
1481
1482 with_settings :non_working_week_days => %w(6 7) do
1482 with_settings :non_working_week_days => %w(6 7) do
1483 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1483 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1484 issue.reschedule_on '2012-10-11'.to_date
1484 issue.reschedule_on '2012-10-11'.to_date
1485 assert_equal '2012-10-11'.to_date, issue.start_date
1485 assert_equal '2012-10-11'.to_date, issue.start_date
1486 assert_equal '2012-10-11'.to_date, issue.due_date
1486 assert_equal '2012-10-11'.to_date, issue.due_date
1487
1487
1488 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1488 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1489 issue.reschedule_on '2012-10-13'.to_date
1489 issue.reschedule_on '2012-10-13'.to_date
1490 assert_equal '2012-10-15'.to_date, issue.start_date
1490 assert_equal '2012-10-15'.to_date, issue.start_date
1491 assert_equal '2012-10-15'.to_date, issue.due_date
1491 assert_equal '2012-10-15'.to_date, issue.due_date
1492 end
1492 end
1493 end
1493 end
1494
1494
1495 def test_reschedule_an_issue_with_start_and_due_dates
1495 def test_reschedule_an_issue_with_start_and_due_dates
1496 with_settings :non_working_week_days => [] do
1496 with_settings :non_working_week_days => [] do
1497 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1497 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1498 issue.reschedule_on '2012-10-13'.to_date
1498 issue.reschedule_on '2012-10-13'.to_date
1499 assert_equal '2012-10-13'.to_date, issue.start_date
1499 assert_equal '2012-10-13'.to_date, issue.start_date
1500 assert_equal '2012-10-19'.to_date, issue.due_date
1500 assert_equal '2012-10-19'.to_date, issue.due_date
1501 end
1501 end
1502
1502
1503 with_settings :non_working_week_days => %w(6 7) do
1503 with_settings :non_working_week_days => %w(6 7) do
1504 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days
1504 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19') # 8 working days
1505 issue.reschedule_on '2012-10-11'.to_date
1505 issue.reschedule_on '2012-10-11'.to_date
1506 assert_equal '2012-10-11'.to_date, issue.start_date
1506 assert_equal '2012-10-11'.to_date, issue.start_date
1507 assert_equal '2012-10-23'.to_date, issue.due_date
1507 assert_equal '2012-10-23'.to_date, issue.due_date
1508
1508
1509 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1509 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1510 issue.reschedule_on '2012-10-13'.to_date
1510 issue.reschedule_on '2012-10-13'.to_date
1511 assert_equal '2012-10-15'.to_date, issue.start_date
1511 assert_equal '2012-10-15'.to_date, issue.start_date
1512 assert_equal '2012-10-25'.to_date, issue.due_date
1512 assert_equal '2012-10-25'.to_date, issue.due_date
1513 end
1513 end
1514 end
1514 end
1515
1515
1516 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1516 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1517 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1517 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1518 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1518 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1519 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1519 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1520 :relation_type => IssueRelation::TYPE_PRECEDES)
1520 :relation_type => IssueRelation::TYPE_PRECEDES)
1521 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1521 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1522
1522
1523 issue1.reload
1523 issue1.reload
1524 issue1.due_date = '2012-10-23'
1524 issue1.due_date = '2012-10-23'
1525 issue1.save!
1525 issue1.save!
1526 issue2.reload
1526 issue2.reload
1527 assert_equal Date.parse('2012-10-24'), issue2.start_date
1527 assert_equal Date.parse('2012-10-24'), issue2.start_date
1528 assert_equal Date.parse('2012-10-26'), issue2.due_date
1528 assert_equal Date.parse('2012-10-26'), issue2.due_date
1529 end
1529 end
1530
1530
1531 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
1531 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
1532 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1532 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1533 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1533 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1534 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1534 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1535 :relation_type => IssueRelation::TYPE_PRECEDES)
1535 :relation_type => IssueRelation::TYPE_PRECEDES)
1536 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1536 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1537
1537
1538 issue1.reload
1538 issue1.reload
1539 issue1.start_date = '2012-09-17'
1539 issue1.start_date = '2012-09-17'
1540 issue1.due_date = '2012-09-18'
1540 issue1.due_date = '2012-09-18'
1541 issue1.save!
1541 issue1.save!
1542 issue2.reload
1542 issue2.reload
1543 assert_equal Date.parse('2012-09-19'), issue2.start_date
1543 assert_equal Date.parse('2012-09-19'), issue2.start_date
1544 assert_equal Date.parse('2012-09-21'), issue2.due_date
1544 assert_equal Date.parse('2012-09-21'), issue2.due_date
1545 end
1545 end
1546
1546
1547 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
1547 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
1548 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1548 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1549 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1549 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1550 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
1550 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
1551 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1551 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1552 :relation_type => IssueRelation::TYPE_PRECEDES)
1552 :relation_type => IssueRelation::TYPE_PRECEDES)
1553 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1553 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1554 :relation_type => IssueRelation::TYPE_PRECEDES)
1554 :relation_type => IssueRelation::TYPE_PRECEDES)
1555 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1555 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1556
1556
1557 issue1.reload
1557 issue1.reload
1558 issue1.start_date = '2012-09-17'
1558 issue1.start_date = '2012-09-17'
1559 issue1.due_date = '2012-09-18'
1559 issue1.due_date = '2012-09-18'
1560 issue1.save!
1560 issue1.save!
1561 issue2.reload
1561 issue2.reload
1562 # Issue 2 must start after Issue 3
1562 # Issue 2 must start after Issue 3
1563 assert_equal Date.parse('2012-10-03'), issue2.start_date
1563 assert_equal Date.parse('2012-10-03'), issue2.start_date
1564 assert_equal Date.parse('2012-10-05'), issue2.due_date
1564 assert_equal Date.parse('2012-10-05'), issue2.due_date
1565 end
1565 end
1566
1566
1567 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1567 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1568 with_settings :non_working_week_days => [] do
1568 with_settings :non_working_week_days => [] do
1569 stale = Issue.find(1)
1569 stale = Issue.find(1)
1570 issue = Issue.find(1)
1570 issue = Issue.find(1)
1571 issue.subject = "Updated"
1571 issue.subject = "Updated"
1572 issue.save!
1572 issue.save!
1573 date = 10.days.from_now.to_date
1573 date = 10.days.from_now.to_date
1574 assert_nothing_raised do
1574 assert_nothing_raised do
1575 stale.reschedule_on!(date)
1575 stale.reschedule_on!(date)
1576 end
1576 end
1577 assert_equal date, stale.reload.start_date
1577 assert_equal date, stale.reload.start_date
1578 end
1578 end
1579 end
1579 end
1580
1580
1581 def test_child_issue_should_consider_parent_soonest_start_on_create
1581 def test_child_issue_should_consider_parent_soonest_start_on_create
1582 set_language_if_valid 'en'
1582 set_language_if_valid 'en'
1583 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1583 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1584 issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
1584 issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
1585 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1585 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1586 :relation_type => IssueRelation::TYPE_PRECEDES)
1586 :relation_type => IssueRelation::TYPE_PRECEDES)
1587 issue1.reload
1587 issue1.reload
1588 issue2.reload
1588 issue2.reload
1589 assert_equal Date.parse('2012-10-18'), issue2.start_date
1589 assert_equal Date.parse('2012-10-18'), issue2.start_date
1590
1590
1591 child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
1591 child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
1592 :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
1592 :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
1593 assert !child.valid?
1593 assert !child.valid?
1594 assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages
1594 assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages
1595 assert_equal Date.parse('2012-10-18'), child.soonest_start
1595 assert_equal Date.parse('2012-10-18'), child.soonest_start
1596 child.start_date = '2012-10-18'
1596 child.start_date = '2012-10-18'
1597 assert child.save
1597 assert child.save
1598 end
1598 end
1599
1599
1600 def test_setting_parent_to_a_dependent_issue_should_not_validate
1600 def test_setting_parent_to_a_dependent_issue_should_not_validate
1601 set_language_if_valid 'en'
1601 set_language_if_valid 'en'
1602 issue1 = Issue.generate!
1602 issue1 = Issue.generate!
1603 issue2 = Issue.generate!
1603 issue2 = Issue.generate!
1604 issue3 = Issue.generate!
1604 issue3 = Issue.generate!
1605 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1605 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1606 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
1606 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
1607 issue3.reload
1607 issue3.reload
1608 issue3.parent_issue_id = issue2.id
1608 issue3.parent_issue_id = issue2.id
1609 assert !issue3.valid?
1609 assert !issue3.valid?
1610 assert_include 'Parent task is invalid', issue3.errors.full_messages
1610 assert_include 'Parent task is invalid', issue3.errors.full_messages
1611 end
1611 end
1612
1612
1613 def test_setting_parent_should_not_allow_circular_dependency
1613 def test_setting_parent_should_not_allow_circular_dependency
1614 set_language_if_valid 'en'
1614 set_language_if_valid 'en'
1615 issue1 = Issue.generate!
1615 issue1 = Issue.generate!
1616 issue2 = Issue.generate!
1616 issue2 = Issue.generate!
1617 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1617 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1618 issue3 = Issue.generate!
1618 issue3 = Issue.generate!
1619 issue2.reload
1619 issue2.reload
1620 issue2.parent_issue_id = issue3.id
1620 issue2.parent_issue_id = issue3.id
1621 issue2.save!
1621 issue2.save!
1622 issue4 = Issue.generate!
1622 issue4 = Issue.generate!
1623 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
1623 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
1624 issue4.reload
1624 issue4.reload
1625 issue4.parent_issue_id = issue1.id
1625 issue4.parent_issue_id = issue1.id
1626 assert !issue4.valid?
1626 assert !issue4.valid?
1627 assert_include 'Parent task is invalid', issue4.errors.full_messages
1627 assert_include 'Parent task is invalid', issue4.errors.full_messages
1628 end
1628 end
1629
1629
1630 def test_overdue
1630 def test_overdue
1631 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1631 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1632 assert !Issue.new(:due_date => Date.today).overdue?
1632 assert !Issue.new(:due_date => Date.today).overdue?
1633 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1633 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1634 assert !Issue.new(:due_date => nil).overdue?
1634 assert !Issue.new(:due_date => nil).overdue?
1635 assert !Issue.new(:due_date => 1.day.ago.to_date,
1635 assert !Issue.new(:due_date => 1.day.ago.to_date,
1636 :status => IssueStatus.where(:is_closed => true).first
1636 :status => IssueStatus.where(:is_closed => true).first
1637 ).overdue?
1637 ).overdue?
1638 end
1638 end
1639
1639
1640 test "#behind_schedule? should be false if the issue has no start_date" do
1640 test "#behind_schedule? should be false if the issue has no start_date" do
1641 assert !Issue.new(:start_date => nil,
1641 assert !Issue.new(:start_date => nil,
1642 :due_date => 1.day.from_now.to_date,
1642 :due_date => 1.day.from_now.to_date,
1643 :done_ratio => 0).behind_schedule?
1643 :done_ratio => 0).behind_schedule?
1644 end
1644 end
1645
1645
1646 test "#behind_schedule? should be false if the issue has no end_date" do
1646 test "#behind_schedule? should be false if the issue has no end_date" do
1647 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1647 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1648 :due_date => nil,
1648 :due_date => nil,
1649 :done_ratio => 0).behind_schedule?
1649 :done_ratio => 0).behind_schedule?
1650 end
1650 end
1651
1651
1652 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
1652 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
1653 assert !Issue.new(:start_date => 50.days.ago.to_date,
1653 assert !Issue.new(:start_date => 50.days.ago.to_date,
1654 :due_date => 50.days.from_now.to_date,
1654 :due_date => 50.days.from_now.to_date,
1655 :done_ratio => 90).behind_schedule?
1655 :done_ratio => 90).behind_schedule?
1656 end
1656 end
1657
1657
1658 test "#behind_schedule? should be true if the issue hasn't been started at all" do
1658 test "#behind_schedule? should be true if the issue hasn't been started at all" do
1659 assert Issue.new(:start_date => 1.day.ago.to_date,
1659 assert Issue.new(:start_date => 1.day.ago.to_date,
1660 :due_date => 1.day.from_now.to_date,
1660 :due_date => 1.day.from_now.to_date,
1661 :done_ratio => 0).behind_schedule?
1661 :done_ratio => 0).behind_schedule?
1662 end
1662 end
1663
1663
1664 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
1664 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
1665 assert Issue.new(:start_date => 100.days.ago.to_date,
1665 assert Issue.new(:start_date => 100.days.ago.to_date,
1666 :due_date => Date.today,
1666 :due_date => Date.today,
1667 :done_ratio => 90).behind_schedule?
1667 :done_ratio => 90).behind_schedule?
1668 end
1668 end
1669
1669
1670 test "#assignable_users should be Users" do
1670 test "#assignable_users should be Users" do
1671 assert_kind_of User, Issue.find(1).assignable_users.first
1671 assert_kind_of User, Issue.find(1).assignable_users.first
1672 end
1672 end
1673
1673
1674 test "#assignable_users should include the issue author" do
1674 test "#assignable_users should include the issue author" do
1675 non_project_member = User.generate!
1675 non_project_member = User.generate!
1676 issue = Issue.generate!(:author => non_project_member)
1676 issue = Issue.generate!(:author => non_project_member)
1677
1677
1678 assert issue.assignable_users.include?(non_project_member)
1678 assert issue.assignable_users.include?(non_project_member)
1679 end
1679 end
1680
1680
1681 test "#assignable_users should include the current assignee" do
1681 test "#assignable_users should include the current assignee" do
1682 user = User.generate!
1682 user = User.generate!
1683 issue = Issue.generate!(:assigned_to => user)
1683 issue = Issue.generate!(:assigned_to => user)
1684 user.lock!
1684 user.lock!
1685
1685
1686 assert Issue.find(issue.id).assignable_users.include?(user)
1686 assert Issue.find(issue.id).assignable_users.include?(user)
1687 end
1687 end
1688
1688
1689 test "#assignable_users should not show the issue author twice" do
1689 test "#assignable_users should not show the issue author twice" do
1690 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1690 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1691 assert_equal 2, assignable_user_ids.length
1691 assert_equal 2, assignable_user_ids.length
1692
1692
1693 assignable_user_ids.each do |user_id|
1693 assignable_user_ids.each do |user_id|
1694 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1694 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1695 "User #{user_id} appears more or less than once"
1695 "User #{user_id} appears more or less than once"
1696 end
1696 end
1697 end
1697 end
1698
1698
1699 test "#assignable_users with issue_group_assignment should include groups" do
1699 test "#assignable_users with issue_group_assignment should include groups" do
1700 issue = Issue.new(:project => Project.find(2))
1700 issue = Issue.new(:project => Project.find(2))
1701
1701
1702 with_settings :issue_group_assignment => '1' do
1702 with_settings :issue_group_assignment => '1' do
1703 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1703 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1704 assert issue.assignable_users.include?(Group.find(11))
1704 assert issue.assignable_users.include?(Group.find(11))
1705 end
1705 end
1706 end
1706 end
1707
1707
1708 test "#assignable_users without issue_group_assignment should not include groups" do
1708 test "#assignable_users without issue_group_assignment should not include groups" do
1709 issue = Issue.new(:project => Project.find(2))
1709 issue = Issue.new(:project => Project.find(2))
1710
1710
1711 with_settings :issue_group_assignment => '0' do
1711 with_settings :issue_group_assignment => '0' do
1712 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1712 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1713 assert !issue.assignable_users.include?(Group.find(11))
1713 assert !issue.assignable_users.include?(Group.find(11))
1714 end
1714 end
1715 end
1715 end
1716
1716
1717 def test_create_should_send_email_notification
1717 def test_create_should_send_email_notification
1718 ActionMailer::Base.deliveries.clear
1718 ActionMailer::Base.deliveries.clear
1719 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1719 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1720 :author_id => 3, :status_id => 1,
1720 :author_id => 3, :status_id => 1,
1721 :priority => IssuePriority.all.first,
1721 :priority => IssuePriority.all.first,
1722 :subject => 'test_create', :estimated_hours => '1:30')
1722 :subject => 'test_create', :estimated_hours => '1:30')
1723 with_settings :notified_events => %w(issue_added) do
1723 with_settings :notified_events => %w(issue_added) do
1724 assert issue.save
1724 assert issue.save
1725 assert_equal 1, ActionMailer::Base.deliveries.size
1725 assert_equal 1, ActionMailer::Base.deliveries.size
1726 end
1726 end
1727 end
1727 end
1728
1728
1729 def test_create_should_send_one_email_notification_with_both_settings
1729 def test_create_should_send_one_email_notification_with_both_settings
1730 ActionMailer::Base.deliveries.clear
1730 ActionMailer::Base.deliveries.clear
1731 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1731 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1732 :author_id => 3, :status_id => 1,
1732 :author_id => 3, :status_id => 1,
1733 :priority => IssuePriority.all.first,
1733 :priority => IssuePriority.all.first,
1734 :subject => 'test_create', :estimated_hours => '1:30')
1734 :subject => 'test_create', :estimated_hours => '1:30')
1735 with_settings :notified_events => %w(issue_added issue_updated) do
1735 with_settings :notified_events => %w(issue_added issue_updated) do
1736 assert issue.save
1736 assert issue.save
1737 assert_equal 1, ActionMailer::Base.deliveries.size
1737 assert_equal 1, ActionMailer::Base.deliveries.size
1738 end
1738 end
1739 end
1739 end
1740
1740
1741 def test_create_should_not_send_email_notification_with_no_setting
1741 def test_create_should_not_send_email_notification_with_no_setting
1742 ActionMailer::Base.deliveries.clear
1742 ActionMailer::Base.deliveries.clear
1743 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1743 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1744 :author_id => 3, :status_id => 1,
1744 :author_id => 3, :status_id => 1,
1745 :priority => IssuePriority.all.first,
1745 :priority => IssuePriority.all.first,
1746 :subject => 'test_create', :estimated_hours => '1:30')
1746 :subject => 'test_create', :estimated_hours => '1:30')
1747 with_settings :notified_events => [] do
1747 with_settings :notified_events => [] do
1748 assert issue.save
1748 assert issue.save
1749 assert_equal 0, ActionMailer::Base.deliveries.size
1749 assert_equal 0, ActionMailer::Base.deliveries.size
1750 end
1750 end
1751 end
1751 end
1752
1752
1753 def test_update_should_notify_previous_assignee
1753 def test_update_should_notify_previous_assignee
1754 ActionMailer::Base.deliveries.clear
1754 ActionMailer::Base.deliveries.clear
1755 user = User.find(3)
1755 user = User.find(3)
1756 user.members.update_all ["mail_notification = ?", false]
1756 user.members.update_all ["mail_notification = ?", false]
1757 user.update_attribute :mail_notification, 'only_assigned'
1757 user.update_attribute :mail_notification, 'only_assigned'
1758
1758
1759 issue = Issue.find(2)
1759 issue = Issue.find(2)
1760 issue.init_journal User.find(1)
1760 issue.init_journal User.find(1)
1761 issue.assigned_to = nil
1761 issue.assigned_to = nil
1762 issue.save!
1762 issue.save!
1763 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
1763 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
1764 end
1764 end
1765
1765
1766 def test_stale_issue_should_not_send_email_notification
1766 def test_stale_issue_should_not_send_email_notification
1767 ActionMailer::Base.deliveries.clear
1767 ActionMailer::Base.deliveries.clear
1768 issue = Issue.find(1)
1768 issue = Issue.find(1)
1769 stale = Issue.find(1)
1769 stale = Issue.find(1)
1770
1770
1771 issue.init_journal(User.find(1))
1771 issue.init_journal(User.find(1))
1772 issue.subject = 'Subjet update'
1772 issue.subject = 'Subjet update'
1773 with_settings :notified_events => %w(issue_updated) do
1773 with_settings :notified_events => %w(issue_updated) do
1774 assert issue.save
1774 assert issue.save
1775 assert_equal 1, ActionMailer::Base.deliveries.size
1775 assert_equal 1, ActionMailer::Base.deliveries.size
1776 ActionMailer::Base.deliveries.clear
1776 ActionMailer::Base.deliveries.clear
1777
1777
1778 stale.init_journal(User.find(1))
1778 stale.init_journal(User.find(1))
1779 stale.subject = 'Another subjet update'
1779 stale.subject = 'Another subjet update'
1780 assert_raise ActiveRecord::StaleObjectError do
1780 assert_raise ActiveRecord::StaleObjectError do
1781 stale.save
1781 stale.save
1782 end
1782 end
1783 assert ActionMailer::Base.deliveries.empty?
1783 assert ActionMailer::Base.deliveries.empty?
1784 end
1784 end
1785 end
1785 end
1786
1786
1787 def test_journalized_description
1787 def test_journalized_description
1788 IssueCustomField.delete_all
1788 IssueCustomField.delete_all
1789
1789
1790 i = Issue.first
1790 i = Issue.first
1791 old_description = i.description
1791 old_description = i.description
1792 new_description = "This is the new description"
1792 new_description = "This is the new description"
1793
1793
1794 i.init_journal(User.find(2))
1794 i.init_journal(User.find(2))
1795 i.description = new_description
1795 i.description = new_description
1796 assert_difference 'Journal.count', 1 do
1796 assert_difference 'Journal.count', 1 do
1797 assert_difference 'JournalDetail.count', 1 do
1797 assert_difference 'JournalDetail.count', 1 do
1798 i.save!
1798 i.save!
1799 end
1799 end
1800 end
1800 end
1801
1801
1802 detail = JournalDetail.order('id DESC').first
1802 detail = JournalDetail.order('id DESC').first
1803 assert_equal i, detail.journal.journalized
1803 assert_equal i, detail.journal.journalized
1804 assert_equal 'attr', detail.property
1804 assert_equal 'attr', detail.property
1805 assert_equal 'description', detail.prop_key
1805 assert_equal 'description', detail.prop_key
1806 assert_equal old_description, detail.old_value
1806 assert_equal old_description, detail.old_value
1807 assert_equal new_description, detail.value
1807 assert_equal new_description, detail.value
1808 end
1808 end
1809
1809
1810 def test_blank_descriptions_should_not_be_journalized
1810 def test_blank_descriptions_should_not_be_journalized
1811 IssueCustomField.delete_all
1811 IssueCustomField.delete_all
1812 Issue.where(:id => 1).update_all("description = NULL")
1812 Issue.where(:id => 1).update_all("description = NULL")
1813
1813
1814 i = Issue.find(1)
1814 i = Issue.find(1)
1815 i.init_journal(User.find(2))
1815 i.init_journal(User.find(2))
1816 i.subject = "blank description"
1816 i.subject = "blank description"
1817 i.description = "\r\n"
1817 i.description = "\r\n"
1818
1818
1819 assert_difference 'Journal.count', 1 do
1819 assert_difference 'Journal.count', 1 do
1820 assert_difference 'JournalDetail.count', 1 do
1820 assert_difference 'JournalDetail.count', 1 do
1821 i.save!
1821 i.save!
1822 end
1822 end
1823 end
1823 end
1824 end
1824 end
1825
1825
1826 def test_journalized_multi_custom_field
1826 def test_journalized_multi_custom_field
1827 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
1827 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
1828 :is_filter => true, :is_for_all => true,
1828 :is_filter => true, :is_for_all => true,
1829 :tracker_ids => [1],
1829 :tracker_ids => [1],
1830 :possible_values => ['value1', 'value2', 'value3'],
1830 :possible_values => ['value1', 'value2', 'value3'],
1831 :multiple => true)
1831 :multiple => true)
1832
1832
1833 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
1833 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
1834 :subject => 'Test', :author_id => 1)
1834 :subject => 'Test', :author_id => 1)
1835
1835
1836 assert_difference 'Journal.count' do
1836 assert_difference 'Journal.count' do
1837 assert_difference 'JournalDetail.count' do
1837 assert_difference 'JournalDetail.count' do
1838 issue.init_journal(User.first)
1838 issue.init_journal(User.first)
1839 issue.custom_field_values = {field.id => ['value1']}
1839 issue.custom_field_values = {field.id => ['value1']}
1840 issue.save!
1840 issue.save!
1841 end
1841 end
1842 assert_difference 'JournalDetail.count' do
1842 assert_difference 'JournalDetail.count' do
1843 issue.init_journal(User.first)
1843 issue.init_journal(User.first)
1844 issue.custom_field_values = {field.id => ['value1', 'value2']}
1844 issue.custom_field_values = {field.id => ['value1', 'value2']}
1845 issue.save!
1845 issue.save!
1846 end
1846 end
1847 assert_difference 'JournalDetail.count', 2 do
1847 assert_difference 'JournalDetail.count', 2 do
1848 issue.init_journal(User.first)
1848 issue.init_journal(User.first)
1849 issue.custom_field_values = {field.id => ['value3', 'value2']}
1849 issue.custom_field_values = {field.id => ['value3', 'value2']}
1850 issue.save!
1850 issue.save!
1851 end
1851 end
1852 assert_difference 'JournalDetail.count', 2 do
1852 assert_difference 'JournalDetail.count', 2 do
1853 issue.init_journal(User.first)
1853 issue.init_journal(User.first)
1854 issue.custom_field_values = {field.id => nil}
1854 issue.custom_field_values = {field.id => nil}
1855 issue.save!
1855 issue.save!
1856 end
1856 end
1857 end
1857 end
1858 end
1858 end
1859
1859
1860 def test_description_eol_should_be_normalized
1860 def test_description_eol_should_be_normalized
1861 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
1861 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
1862 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
1862 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
1863 end
1863 end
1864
1864
1865 def test_saving_twice_should_not_duplicate_journal_details
1865 def test_saving_twice_should_not_duplicate_journal_details
1866 i = Issue.first
1866 i = Issue.first
1867 i.init_journal(User.find(2), 'Some notes')
1867 i.init_journal(User.find(2), 'Some notes')
1868 # initial changes
1868 # initial changes
1869 i.subject = 'New subject'
1869 i.subject = 'New subject'
1870 i.done_ratio = i.done_ratio + 10
1870 i.done_ratio = i.done_ratio + 10
1871 assert_difference 'Journal.count' do
1871 assert_difference 'Journal.count' do
1872 assert i.save
1872 assert i.save
1873 end
1873 end
1874 # 1 more change
1874 # 1 more change
1875 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
1875 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
1876 assert_no_difference 'Journal.count' do
1876 assert_no_difference 'Journal.count' do
1877 assert_difference 'JournalDetail.count', 1 do
1877 assert_difference 'JournalDetail.count', 1 do
1878 i.save
1878 i.save
1879 end
1879 end
1880 end
1880 end
1881 # no more change
1881 # no more change
1882 assert_no_difference 'Journal.count' do
1882 assert_no_difference 'Journal.count' do
1883 assert_no_difference 'JournalDetail.count' do
1883 assert_no_difference 'JournalDetail.count' do
1884 i.save
1884 i.save
1885 end
1885 end
1886 end
1886 end
1887 end
1887 end
1888
1888
1889 def test_all_dependent_issues
1889 def test_all_dependent_issues
1890 IssueRelation.delete_all
1890 IssueRelation.delete_all
1891 assert IssueRelation.create!(:issue_from => Issue.find(1),
1891 assert IssueRelation.create!(:issue_from => Issue.find(1),
1892 :issue_to => Issue.find(2),
1892 :issue_to => Issue.find(2),
1893 :relation_type => IssueRelation::TYPE_PRECEDES)
1893 :relation_type => IssueRelation::TYPE_PRECEDES)
1894 assert IssueRelation.create!(:issue_from => Issue.find(2),
1894 assert IssueRelation.create!(:issue_from => Issue.find(2),
1895 :issue_to => Issue.find(3),
1895 :issue_to => Issue.find(3),
1896 :relation_type => IssueRelation::TYPE_PRECEDES)
1896 :relation_type => IssueRelation::TYPE_PRECEDES)
1897 assert IssueRelation.create!(:issue_from => Issue.find(3),
1897 assert IssueRelation.create!(:issue_from => Issue.find(3),
1898 :issue_to => Issue.find(8),
1898 :issue_to => Issue.find(8),
1899 :relation_type => IssueRelation::TYPE_PRECEDES)
1899 :relation_type => IssueRelation::TYPE_PRECEDES)
1900
1900
1901 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1901 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
1902 end
1902 end
1903
1903
1904 def test_all_dependent_issues_with_subtask
1904 def test_all_dependent_issues_with_subtask
1905 IssueRelation.delete_all
1905 IssueRelation.delete_all
1906
1906
1907 project = Project.generate!(:name => "testproject")
1907 project = Project.generate!(:name => "testproject")
1908
1908
1909 parentIssue = Issue.generate!(:project => project)
1909 parentIssue = Issue.generate!(:project => project)
1910 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1910 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1911 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1911 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1912
1912
1913 assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
1913 assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
1914 end
1914 end
1915
1915
1916 def test_all_dependent_issues_does_not_include_self
1916 def test_all_dependent_issues_does_not_include_self
1917 IssueRelation.delete_all
1917 IssueRelation.delete_all
1918
1918
1919 project = Project.generate!(:name => "testproject")
1919 project = Project.generate!(:name => "testproject")
1920
1920
1921 parentIssue = Issue.generate!(:project => project)
1921 parentIssue = Issue.generate!(:project => project)
1922 childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1922 childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1923
1923
1924 assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
1924 assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
1925 end
1925 end
1926
1926
1927 def test_all_dependent_issues_with_parenttask_and_sibling
1927 def test_all_dependent_issues_with_parenttask_and_sibling
1928 IssueRelation.delete_all
1928 IssueRelation.delete_all
1929
1929
1930 project = Project.generate!(:name => "testproject")
1930 project = Project.generate!(:name => "testproject")
1931
1931
1932 parentIssue = Issue.generate!(:project => project)
1932 parentIssue = Issue.generate!(:project => project)
1933 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1933 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1934 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1934 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
1935
1935
1936 assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
1936 assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
1937 end
1937 end
1938
1938
1939 def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
1939 def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
1940 IssueRelation.delete_all
1940 IssueRelation.delete_all
1941
1941
1942 project = Project.generate!(:name => "testproject")
1942 project = Project.generate!(:name => "testproject")
1943
1943
1944 parentIssue1 = Issue.generate!(:project => project)
1944 parentIssue1 = Issue.generate!(:project => project)
1945 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1945 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1946 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1946 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1947
1947
1948 parentIssue2 = Issue.generate!(:project => project)
1948 parentIssue2 = Issue.generate!(:project => project)
1949 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1949 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1950 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1950 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1951
1951
1952
1952
1953 assert IssueRelation.create(:issue_from => parentIssue1,
1953 assert IssueRelation.create(:issue_from => parentIssue1,
1954 :issue_to => childIssue2_2,
1954 :issue_to => childIssue2_2,
1955 :relation_type => IssueRelation::TYPE_BLOCKS)
1955 :relation_type => IssueRelation::TYPE_BLOCKS)
1956
1956
1957 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
1957 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
1958 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
1958 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
1959 end
1959 end
1960
1960
1961 def test_all_dependent_issues_with_relation_to_parent_in_other_tree
1961 def test_all_dependent_issues_with_relation_to_parent_in_other_tree
1962 IssueRelation.delete_all
1962 IssueRelation.delete_all
1963
1963
1964 project = Project.generate!(:name => "testproject")
1964 project = Project.generate!(:name => "testproject")
1965
1965
1966 parentIssue1 = Issue.generate!(:project => project)
1966 parentIssue1 = Issue.generate!(:project => project)
1967 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1967 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1968 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1968 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1969
1969
1970 parentIssue2 = Issue.generate!(:project => project)
1970 parentIssue2 = Issue.generate!(:project => project)
1971 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1971 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1972 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1972 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1973
1973
1974
1974
1975 assert IssueRelation.create(:issue_from => parentIssue1,
1975 assert IssueRelation.create(:issue_from => parentIssue1,
1976 :issue_to => parentIssue2,
1976 :issue_to => parentIssue2,
1977 :relation_type => IssueRelation::TYPE_BLOCKS)
1977 :relation_type => IssueRelation::TYPE_BLOCKS)
1978
1978
1979 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
1979 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
1980 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
1980 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
1981 end
1981 end
1982
1982
1983 def test_all_dependent_issues_with_transitive_relation
1983 def test_all_dependent_issues_with_transitive_relation
1984 IssueRelation.delete_all
1984 IssueRelation.delete_all
1985
1985
1986 project = Project.generate!(:name => "testproject")
1986 project = Project.generate!(:name => "testproject")
1987
1987
1988 parentIssue1 = Issue.generate!(:project => project)
1988 parentIssue1 = Issue.generate!(:project => project)
1989 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1989 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
1990
1990
1991 parentIssue2 = Issue.generate!(:project => project)
1991 parentIssue2 = Issue.generate!(:project => project)
1992 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1992 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
1993
1993
1994 independentIssue = Issue.generate!(:project => project)
1994 independentIssue = Issue.generate!(:project => project)
1995
1995
1996 assert IssueRelation.create(:issue_from => parentIssue1,
1996 assert IssueRelation.create(:issue_from => parentIssue1,
1997 :issue_to => childIssue2_1,
1997 :issue_to => childIssue2_1,
1998 :relation_type => IssueRelation::TYPE_RELATES)
1998 :relation_type => IssueRelation::TYPE_RELATES)
1999
1999
2000 assert IssueRelation.create(:issue_from => childIssue2_1,
2000 assert IssueRelation.create(:issue_from => childIssue2_1,
2001 :issue_to => independentIssue,
2001 :issue_to => independentIssue,
2002 :relation_type => IssueRelation::TYPE_RELATES)
2002 :relation_type => IssueRelation::TYPE_RELATES)
2003
2003
2004 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2004 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2005 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2005 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2006 end
2006 end
2007
2007
2008 def test_all_dependent_issues_with_transitive_relation2
2008 def test_all_dependent_issues_with_transitive_relation2
2009 IssueRelation.delete_all
2009 IssueRelation.delete_all
2010
2010
2011 project = Project.generate!(:name => "testproject")
2011 project = Project.generate!(:name => "testproject")
2012
2012
2013 parentIssue1 = Issue.generate!(:project => project)
2013 parentIssue1 = Issue.generate!(:project => project)
2014 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2014 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2015
2015
2016 parentIssue2 = Issue.generate!(:project => project)
2016 parentIssue2 = Issue.generate!(:project => project)
2017 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2017 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2018
2018
2019 independentIssue = Issue.generate!(:project => project)
2019 independentIssue = Issue.generate!(:project => project)
2020
2020
2021 assert IssueRelation.create(:issue_from => parentIssue1,
2021 assert IssueRelation.create(:issue_from => parentIssue1,
2022 :issue_to => independentIssue,
2022 :issue_to => independentIssue,
2023 :relation_type => IssueRelation::TYPE_RELATES)
2023 :relation_type => IssueRelation::TYPE_RELATES)
2024
2024
2025 assert IssueRelation.create(:issue_from => independentIssue,
2025 assert IssueRelation.create(:issue_from => independentIssue,
2026 :issue_to => childIssue2_1,
2026 :issue_to => childIssue2_1,
2027 :relation_type => IssueRelation::TYPE_RELATES)
2027 :relation_type => IssueRelation::TYPE_RELATES)
2028
2028
2029 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2029 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2030 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2030 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2031
2031
2032 end
2032 end
2033
2033
2034 def test_all_dependent_issues_with_persistent_circular_dependency
2034 def test_all_dependent_issues_with_persistent_circular_dependency
2035 IssueRelation.delete_all
2035 IssueRelation.delete_all
2036 assert IssueRelation.create!(:issue_from => Issue.find(1),
2036 assert IssueRelation.create!(:issue_from => Issue.find(1),
2037 :issue_to => Issue.find(2),
2037 :issue_to => Issue.find(2),
2038 :relation_type => IssueRelation::TYPE_PRECEDES)
2038 :relation_type => IssueRelation::TYPE_PRECEDES)
2039 assert IssueRelation.create!(:issue_from => Issue.find(2),
2039 assert IssueRelation.create!(:issue_from => Issue.find(2),
2040 :issue_to => Issue.find(3),
2040 :issue_to => Issue.find(3),
2041 :relation_type => IssueRelation::TYPE_PRECEDES)
2041 :relation_type => IssueRelation::TYPE_PRECEDES)
2042
2042
2043 r = IssueRelation.create!(:issue_from => Issue.find(3),
2043 r = IssueRelation.create!(:issue_from => Issue.find(3),
2044 :issue_to => Issue.find(7),
2044 :issue_to => Issue.find(7),
2045 :relation_type => IssueRelation::TYPE_PRECEDES)
2045 :relation_type => IssueRelation::TYPE_PRECEDES)
2046 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2046 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2047
2047
2048 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
2048 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
2049 end
2049 end
2050
2050
2051 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
2051 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
2052 IssueRelation.delete_all
2052 IssueRelation.delete_all
2053 assert IssueRelation.create!(:issue_from => Issue.find(1),
2053 assert IssueRelation.create!(:issue_from => Issue.find(1),
2054 :issue_to => Issue.find(2),
2054 :issue_to => Issue.find(2),
2055 :relation_type => IssueRelation::TYPE_RELATES)
2055 :relation_type => IssueRelation::TYPE_RELATES)
2056 assert IssueRelation.create!(:issue_from => Issue.find(2),
2056 assert IssueRelation.create!(:issue_from => Issue.find(2),
2057 :issue_to => Issue.find(3),
2057 :issue_to => Issue.find(3),
2058 :relation_type => IssueRelation::TYPE_RELATES)
2058 :relation_type => IssueRelation::TYPE_RELATES)
2059 assert IssueRelation.create!(:issue_from => Issue.find(3),
2059 assert IssueRelation.create!(:issue_from => Issue.find(3),
2060 :issue_to => Issue.find(8),
2060 :issue_to => Issue.find(8),
2061 :relation_type => IssueRelation::TYPE_RELATES)
2061 :relation_type => IssueRelation::TYPE_RELATES)
2062
2062
2063 r = IssueRelation.create!(:issue_from => Issue.find(8),
2063 r = IssueRelation.create!(:issue_from => Issue.find(8),
2064 :issue_to => Issue.find(7),
2064 :issue_to => Issue.find(7),
2065 :relation_type => IssueRelation::TYPE_RELATES)
2065 :relation_type => IssueRelation::TYPE_RELATES)
2066 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 2")
2066 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 2")
2067
2067
2068 r = IssueRelation.create!(:issue_from => Issue.find(3),
2068 r = IssueRelation.create!(:issue_from => Issue.find(3),
2069 :issue_to => Issue.find(7),
2069 :issue_to => Issue.find(7),
2070 :relation_type => IssueRelation::TYPE_RELATES)
2070 :relation_type => IssueRelation::TYPE_RELATES)
2071 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2071 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2072
2072
2073 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2073 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2074 end
2074 end
2075
2075
2076 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2076 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2077 @issue = Issue.find(1)
2077 @issue = Issue.find(1)
2078 @issue_status = IssueStatus.find(1)
2078 @issue_status = IssueStatus.find(1)
2079 @issue_status.update_attribute(:default_done_ratio, 50)
2079 @issue_status.update_attribute(:default_done_ratio, 50)
2080 @issue2 = Issue.find(2)
2080 @issue2 = Issue.find(2)
2081 @issue_status2 = IssueStatus.find(2)
2081 @issue_status2 = IssueStatus.find(2)
2082 @issue_status2.update_attribute(:default_done_ratio, 0)
2082 @issue_status2.update_attribute(:default_done_ratio, 0)
2083
2083
2084 with_settings :issue_done_ratio => 'issue_field' do
2084 with_settings :issue_done_ratio => 'issue_field' do
2085 assert_equal 0, @issue.done_ratio
2085 assert_equal 0, @issue.done_ratio
2086 assert_equal 30, @issue2.done_ratio
2086 assert_equal 30, @issue2.done_ratio
2087 end
2087 end
2088
2088
2089 with_settings :issue_done_ratio => 'issue_status' do
2089 with_settings :issue_done_ratio => 'issue_status' do
2090 assert_equal 50, @issue.done_ratio
2090 assert_equal 50, @issue.done_ratio
2091 assert_equal 0, @issue2.done_ratio
2091 assert_equal 0, @issue2.done_ratio
2092 end
2092 end
2093 end
2093 end
2094
2094
2095 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2095 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2096 @issue = Issue.find(1)
2096 @issue = Issue.find(1)
2097 @issue_status = IssueStatus.find(1)
2097 @issue_status = IssueStatus.find(1)
2098 @issue_status.update_attribute(:default_done_ratio, 50)
2098 @issue_status.update_attribute(:default_done_ratio, 50)
2099 @issue2 = Issue.find(2)
2099 @issue2 = Issue.find(2)
2100 @issue_status2 = IssueStatus.find(2)
2100 @issue_status2 = IssueStatus.find(2)
2101 @issue_status2.update_attribute(:default_done_ratio, 0)
2101 @issue_status2.update_attribute(:default_done_ratio, 0)
2102
2102
2103 with_settings :issue_done_ratio => 'issue_field' do
2103 with_settings :issue_done_ratio => 'issue_field' do
2104 @issue.update_done_ratio_from_issue_status
2104 @issue.update_done_ratio_from_issue_status
2105 @issue2.update_done_ratio_from_issue_status
2105 @issue2.update_done_ratio_from_issue_status
2106
2106
2107 assert_equal 0, @issue.read_attribute(:done_ratio)
2107 assert_equal 0, @issue.read_attribute(:done_ratio)
2108 assert_equal 30, @issue2.read_attribute(:done_ratio)
2108 assert_equal 30, @issue2.read_attribute(:done_ratio)
2109 end
2109 end
2110
2110
2111 with_settings :issue_done_ratio => 'issue_status' do
2111 with_settings :issue_done_ratio => 'issue_status' do
2112 @issue.update_done_ratio_from_issue_status
2112 @issue.update_done_ratio_from_issue_status
2113 @issue2.update_done_ratio_from_issue_status
2113 @issue2.update_done_ratio_from_issue_status
2114
2114
2115 assert_equal 50, @issue.read_attribute(:done_ratio)
2115 assert_equal 50, @issue.read_attribute(:done_ratio)
2116 assert_equal 0, @issue2.read_attribute(:done_ratio)
2116 assert_equal 0, @issue2.read_attribute(:done_ratio)
2117 end
2117 end
2118 end
2118 end
2119
2119
2120 test "#by_tracker" do
2120 test "#by_tracker" do
2121 User.current = User.anonymous
2121 User.current = User.anonymous
2122 groups = Issue.by_tracker(Project.find(1))
2122 groups = Issue.by_tracker(Project.find(1))
2123 assert_equal 3, groups.count
2123 assert_equal 3, groups.count
2124 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2124 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2125 end
2125 end
2126
2126
2127 test "#by_version" do
2127 test "#by_version" do
2128 User.current = User.anonymous
2128 User.current = User.anonymous
2129 groups = Issue.by_version(Project.find(1))
2129 groups = Issue.by_version(Project.find(1))
2130 assert_equal 3, groups.count
2130 assert_equal 3, groups.count
2131 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2131 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2132 end
2132 end
2133
2133
2134 test "#by_priority" do
2134 test "#by_priority" do
2135 User.current = User.anonymous
2135 User.current = User.anonymous
2136 groups = Issue.by_priority(Project.find(1))
2136 groups = Issue.by_priority(Project.find(1))
2137 assert_equal 4, groups.count
2137 assert_equal 4, groups.count
2138 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2138 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2139 end
2139 end
2140
2140
2141 test "#by_category" do
2141 test "#by_category" do
2142 User.current = User.anonymous
2142 User.current = User.anonymous
2143 groups = Issue.by_category(Project.find(1))
2143 groups = Issue.by_category(Project.find(1))
2144 assert_equal 2, groups.count
2144 assert_equal 2, groups.count
2145 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2145 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2146 end
2146 end
2147
2147
2148 test "#by_assigned_to" do
2148 test "#by_assigned_to" do
2149 User.current = User.anonymous
2149 User.current = User.anonymous
2150 groups = Issue.by_assigned_to(Project.find(1))
2150 groups = Issue.by_assigned_to(Project.find(1))
2151 assert_equal 2, groups.count
2151 assert_equal 2, groups.count
2152 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2152 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2153 end
2153 end
2154
2154
2155 test "#by_author" do
2155 test "#by_author" do
2156 User.current = User.anonymous
2156 User.current = User.anonymous
2157 groups = Issue.by_author(Project.find(1))
2157 groups = Issue.by_author(Project.find(1))
2158 assert_equal 4, groups.count
2158 assert_equal 4, groups.count
2159 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2159 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2160 end
2160 end
2161
2161
2162 test "#by_subproject" do
2162 test "#by_subproject" do
2163 User.current = User.anonymous
2163 User.current = User.anonymous
2164 groups = Issue.by_subproject(Project.find(1))
2164 groups = Issue.by_subproject(Project.find(1))
2165 # Private descendant not visible
2165 # Private descendant not visible
2166 assert_equal 1, groups.count
2166 assert_equal 1, groups.count
2167 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2167 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2168 end
2168 end
2169
2169
2170 def test_recently_updated_scope
2170 def test_recently_updated_scope
2171 #should return the last updated issue
2171 #should return the last updated issue
2172 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2172 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2173 end
2173 end
2174
2174
2175 def test_on_active_projects_scope
2175 def test_on_active_projects_scope
2176 assert Project.find(2).archive
2176 assert Project.find(2).archive
2177
2177
2178 before = Issue.on_active_project.length
2178 before = Issue.on_active_project.length
2179 # test inclusion to results
2179 # test inclusion to results
2180 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2180 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2181 assert_equal before + 1, Issue.on_active_project.length
2181 assert_equal before + 1, Issue.on_active_project.length
2182
2182
2183 # Move to an archived project
2183 # Move to an archived project
2184 issue.project = Project.find(2)
2184 issue.project = Project.find(2)
2185 assert issue.save
2185 assert issue.save
2186 assert_equal before, Issue.on_active_project.length
2186 assert_equal before, Issue.on_active_project.length
2187 end
2187 end
2188
2188
2189 test "Issue#recipients should include project recipients" do
2189 test "Issue#recipients should include project recipients" do
2190 issue = Issue.generate!
2190 issue = Issue.generate!
2191 assert issue.project.recipients.present?
2191 assert issue.project.recipients.present?
2192 issue.project.recipients.each do |project_recipient|
2192 issue.project.recipients.each do |project_recipient|
2193 assert issue.recipients.include?(project_recipient)
2193 assert issue.recipients.include?(project_recipient)
2194 end
2194 end
2195 end
2195 end
2196
2196
2197 test "Issue#recipients should include the author if the author is active" do
2197 test "Issue#recipients should include the author if the author is active" do
2198 issue = Issue.generate!(:author => User.generate!)
2198 issue = Issue.generate!(:author => User.generate!)
2199 assert issue.author, "No author set for Issue"
2199 assert issue.author, "No author set for Issue"
2200 assert issue.recipients.include?(issue.author.mail)
2200 assert issue.recipients.include?(issue.author.mail)
2201 end
2201 end
2202
2202
2203 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2203 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2204 issue = Issue.generate!(:assigned_to => User.generate!)
2204 issue = Issue.generate!(:assigned_to => User.generate!)
2205 assert issue.assigned_to, "No assigned_to set for Issue"
2205 assert issue.assigned_to, "No assigned_to set for Issue"
2206 assert issue.recipients.include?(issue.assigned_to.mail)
2206 assert issue.recipients.include?(issue.assigned_to.mail)
2207 end
2207 end
2208
2208
2209 test "Issue#recipients should not include users who opt out of all email" do
2209 test "Issue#recipients should not include users who opt out of all email" do
2210 issue = Issue.generate!(:author => User.generate!)
2210 issue = Issue.generate!(:author => User.generate!)
2211 issue.author.update_attribute(:mail_notification, :none)
2211 issue.author.update_attribute(:mail_notification, :none)
2212 assert !issue.recipients.include?(issue.author.mail)
2212 assert !issue.recipients.include?(issue.author.mail)
2213 end
2213 end
2214
2214
2215 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2215 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2216 issue = Issue.generate!(:author => User.generate!)
2216 issue = Issue.generate!(:author => User.generate!)
2217 issue.author.update_attribute(:mail_notification, :only_assigned)
2217 issue.author.update_attribute(:mail_notification, :only_assigned)
2218 assert !issue.recipients.include?(issue.author.mail)
2218 assert !issue.recipients.include?(issue.author.mail)
2219 end
2219 end
2220
2220
2221 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2221 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2222 issue = Issue.generate!(:assigned_to => User.generate!)
2222 issue = Issue.generate!(:assigned_to => User.generate!)
2223 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
2223 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
2224 assert !issue.recipients.include?(issue.assigned_to.mail)
2224 assert !issue.recipients.include?(issue.assigned_to.mail)
2225 end
2225 end
2226
2226
2227 def test_last_journal_id_with_journals_should_return_the_journal_id
2227 def test_last_journal_id_with_journals_should_return_the_journal_id
2228 assert_equal 2, Issue.find(1).last_journal_id
2228 assert_equal 2, Issue.find(1).last_journal_id
2229 end
2229 end
2230
2230
2231 def test_last_journal_id_without_journals_should_return_nil
2231 def test_last_journal_id_without_journals_should_return_nil
2232 assert_nil Issue.find(3).last_journal_id
2232 assert_nil Issue.find(3).last_journal_id
2233 end
2233 end
2234
2234
2235 def test_journals_after_should_return_journals_with_greater_id
2235 def test_journals_after_should_return_journals_with_greater_id
2236 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2236 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2237 assert_equal [], Issue.find(1).journals_after('2')
2237 assert_equal [], Issue.find(1).journals_after('2')
2238 end
2238 end
2239
2239
2240 def test_journals_after_with_blank_arg_should_return_all_journals
2240 def test_journals_after_with_blank_arg_should_return_all_journals
2241 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2241 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2242 end
2242 end
2243
2243
2244 def test_css_classes_should_include_tracker
2244 def test_css_classes_should_include_tracker
2245 issue = Issue.new(:tracker => Tracker.find(2))
2245 issue = Issue.new(:tracker => Tracker.find(2))
2246 classes = issue.css_classes.split(' ')
2246 classes = issue.css_classes.split(' ')
2247 assert_include 'tracker-2', classes
2247 assert_include 'tracker-2', classes
2248 end
2248 end
2249
2249
2250 def test_css_classes_should_include_priority
2250 def test_css_classes_should_include_priority
2251 issue = Issue.new(:priority => IssuePriority.find(8))
2251 issue = Issue.new(:priority => IssuePriority.find(8))
2252 classes = issue.css_classes.split(' ')
2252 classes = issue.css_classes.split(' ')
2253 assert_include 'priority-8', classes
2253 assert_include 'priority-8', classes
2254 assert_include 'priority-highest', classes
2254 assert_include 'priority-highest', classes
2255 end
2255 end
2256
2256
2257 def test_css_classes_should_include_user_assignment
2257 def test_css_classes_should_include_user_and_group_assignment
2258 issue = Issue.generate(:assigned_to_id => 2)
2258 project = Project.first
2259 assert_include 'assigned-to-me', issue.css_classes(User.find(2))
2259 user = User.generate!
2260 assert_not_include 'assigned-to-me', issue.css_classes(User.find(3))
2260 group = Group.generate!
2261 end
2261 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
2262
2262 group.users << user
2263 def test_css_classes_should_include_user_group_assignment
2263 assert user.member_of?(project)
2264 issue = Issue.generate(:assigned_to_id => 10)
2264 issue1 = Issue.generate(:assigned_to_id => group.id)
2265 assert_include 'assigned-to-my-group', issue.css_classes(Group.find(10).users.first)
2265 assert_include 'assigned-to-my-group', issue1.css_classes(user)
2266 assert_not_include 'assigned-to-my-group', issue.css_classes(User.find(3))
2266 assert_not_include 'assigned-to-me', issue1.css_classes(user)
2267 issue2 = Issue.generate(:assigned_to_id => user.id)
2268 assert_not_include 'assigned-to-my-group', issue2.css_classes(user)
2269 assert_include 'assigned-to-me', issue2.css_classes(user)
2267 end
2270 end
2268
2271
2269 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2272 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2270 set_tmp_attachments_directory
2273 set_tmp_attachments_directory
2271 issue = Issue.generate!
2274 issue = Issue.generate!
2272 issue.save_attachments({
2275 issue.save_attachments({
2273 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2276 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2274 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2277 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2275 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2278 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2276 })
2279 })
2277 issue.attach_saved_attachments
2280 issue.attach_saved_attachments
2278
2281
2279 assert_equal 3, issue.reload.attachments.count
2282 assert_equal 3, issue.reload.attachments.count
2280 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2283 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2281 end
2284 end
2282
2285
2283 def test_closed_on_should_be_nil_when_creating_an_open_issue
2286 def test_closed_on_should_be_nil_when_creating_an_open_issue
2284 issue = Issue.generate!(:status_id => 1).reload
2287 issue = Issue.generate!(:status_id => 1).reload
2285 assert !issue.closed?
2288 assert !issue.closed?
2286 assert_nil issue.closed_on
2289 assert_nil issue.closed_on
2287 end
2290 end
2288
2291
2289 def test_closed_on_should_be_set_when_creating_a_closed_issue
2292 def test_closed_on_should_be_set_when_creating_a_closed_issue
2290 issue = Issue.generate!(:status_id => 5).reload
2293 issue = Issue.generate!(:status_id => 5).reload
2291 assert issue.closed?
2294 assert issue.closed?
2292 assert_not_nil issue.closed_on
2295 assert_not_nil issue.closed_on
2293 assert_equal issue.updated_on, issue.closed_on
2296 assert_equal issue.updated_on, issue.closed_on
2294 assert_equal issue.created_on, issue.closed_on
2297 assert_equal issue.created_on, issue.closed_on
2295 end
2298 end
2296
2299
2297 def test_closed_on_should_be_nil_when_updating_an_open_issue
2300 def test_closed_on_should_be_nil_when_updating_an_open_issue
2298 issue = Issue.find(1)
2301 issue = Issue.find(1)
2299 issue.subject = 'Not closed yet'
2302 issue.subject = 'Not closed yet'
2300 issue.save!
2303 issue.save!
2301 issue.reload
2304 issue.reload
2302 assert_nil issue.closed_on
2305 assert_nil issue.closed_on
2303 end
2306 end
2304
2307
2305 def test_closed_on_should_be_set_when_closing_an_open_issue
2308 def test_closed_on_should_be_set_when_closing_an_open_issue
2306 issue = Issue.find(1)
2309 issue = Issue.find(1)
2307 issue.subject = 'Now closed'
2310 issue.subject = 'Now closed'
2308 issue.status_id = 5
2311 issue.status_id = 5
2309 issue.save!
2312 issue.save!
2310 issue.reload
2313 issue.reload
2311 assert_not_nil issue.closed_on
2314 assert_not_nil issue.closed_on
2312 assert_equal issue.updated_on, issue.closed_on
2315 assert_equal issue.updated_on, issue.closed_on
2313 end
2316 end
2314
2317
2315 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2318 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2316 issue = Issue.open(false).first
2319 issue = Issue.open(false).first
2317 was_closed_on = issue.closed_on
2320 was_closed_on = issue.closed_on
2318 assert_not_nil was_closed_on
2321 assert_not_nil was_closed_on
2319 issue.subject = 'Updating a closed issue'
2322 issue.subject = 'Updating a closed issue'
2320 issue.save!
2323 issue.save!
2321 issue.reload
2324 issue.reload
2322 assert_equal was_closed_on, issue.closed_on
2325 assert_equal was_closed_on, issue.closed_on
2323 end
2326 end
2324
2327
2325 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2328 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2326 issue = Issue.open(false).first
2329 issue = Issue.open(false).first
2327 was_closed_on = issue.closed_on
2330 was_closed_on = issue.closed_on
2328 assert_not_nil was_closed_on
2331 assert_not_nil was_closed_on
2329 issue.subject = 'Reopening a closed issue'
2332 issue.subject = 'Reopening a closed issue'
2330 issue.status_id = 1
2333 issue.status_id = 1
2331 issue.save!
2334 issue.save!
2332 issue.reload
2335 issue.reload
2333 assert !issue.closed?
2336 assert !issue.closed?
2334 assert_equal was_closed_on, issue.closed_on
2337 assert_equal was_closed_on, issue.closed_on
2335 end
2338 end
2336
2339
2337 def test_status_was_should_return_nil_for_new_issue
2340 def test_status_was_should_return_nil_for_new_issue
2338 issue = Issue.new
2341 issue = Issue.new
2339 assert_nil issue.status_was
2342 assert_nil issue.status_was
2340 end
2343 end
2341
2344
2342 def test_status_was_should_return_status_before_change
2345 def test_status_was_should_return_status_before_change
2343 issue = Issue.find(1)
2346 issue = Issue.find(1)
2344 issue.status = IssueStatus.find(2)
2347 issue.status = IssueStatus.find(2)
2345 assert_equal IssueStatus.find(1), issue.status_was
2348 assert_equal IssueStatus.find(1), issue.status_was
2346 end
2349 end
2347
2350
2348 def test_status_was_should_be_reset_on_save
2351 def test_status_was_should_be_reset_on_save
2349 issue = Issue.find(1)
2352 issue = Issue.find(1)
2350 issue.status = IssueStatus.find(2)
2353 issue.status = IssueStatus.find(2)
2351 assert_equal IssueStatus.find(1), issue.status_was
2354 assert_equal IssueStatus.find(1), issue.status_was
2352 assert issue.save!
2355 assert issue.save!
2353 assert_equal IssueStatus.find(2), issue.status_was
2356 assert_equal IssueStatus.find(2), issue.status_was
2354 end
2357 end
2355 end
2358 end
General Comments 0
You need to be logged in to leave comments. Login now