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