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