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