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