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