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