##// END OF EJS Templates
Merged r15196 (#22072)....
Jean-Philippe Lang -
r14826:9b8e29741966
parent child
Show More
@@ -1,1650 +1,1651
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 && author.active?
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 duplicate.private_notes = @current_journal.private_notes
1590 end
1591 end
1591 duplicate.update_attribute :status, self.status
1592 duplicate.update_attribute :status, self.status
1592 end
1593 end
1593 end
1594 end
1594 end
1595 end
1595
1596
1596 # Make sure updated_on is updated when adding a note and set updated_on now
1597 # 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
1598 # so we can set closed_on with the same value on closing
1598 def force_updated_on_change
1599 def force_updated_on_change
1599 if @current_journal || changed?
1600 if @current_journal || changed?
1600 self.updated_on = current_time_from_proper_timezone
1601 self.updated_on = current_time_from_proper_timezone
1601 if new_record?
1602 if new_record?
1602 self.created_on = updated_on
1603 self.created_on = updated_on
1603 end
1604 end
1604 end
1605 end
1605 end
1606 end
1606
1607
1607 # Callback for setting closed_on when the issue is closed.
1608 # Callback for setting closed_on when the issue is closed.
1608 # The closed_on attribute stores the time of the last closing
1609 # The closed_on attribute stores the time of the last closing
1609 # and is preserved when the issue is reopened.
1610 # and is preserved when the issue is reopened.
1610 def update_closed_on
1611 def update_closed_on
1611 if closing?
1612 if closing?
1612 self.closed_on = updated_on
1613 self.closed_on = updated_on
1613 end
1614 end
1614 end
1615 end
1615
1616
1616 # Saves the changes in a Journal
1617 # Saves the changes in a Journal
1617 # Called after_save
1618 # Called after_save
1618 def create_journal
1619 def create_journal
1619 if current_journal
1620 if current_journal
1620 current_journal.save
1621 current_journal.save
1621 end
1622 end
1622 end
1623 end
1623
1624
1624 def send_notification
1625 def send_notification
1625 if Setting.notified_events.include?('issue_added')
1626 if Setting.notified_events.include?('issue_added')
1626 Mailer.deliver_issue_add(self)
1627 Mailer.deliver_issue_add(self)
1627 end
1628 end
1628 end
1629 end
1629
1630
1630 # Stores the previous assignee so we can still have access
1631 # Stores the previous assignee so we can still have access
1631 # to it during after_save callbacks (assigned_to_id_was is reset)
1632 # to it during after_save callbacks (assigned_to_id_was is reset)
1632 def set_assigned_to_was
1633 def set_assigned_to_was
1633 @previous_assigned_to_id = assigned_to_id_was
1634 @previous_assigned_to_id = assigned_to_id_was
1634 end
1635 end
1635
1636
1636 # Clears the previous assignee at the end of after_save callbacks
1637 # Clears the previous assignee at the end of after_save callbacks
1637 def clear_assigned_to_was
1638 def clear_assigned_to_was
1638 @assigned_to_was = nil
1639 @assigned_to_was = nil
1639 @previous_assigned_to_id = nil
1640 @previous_assigned_to_id = nil
1640 end
1641 end
1641
1642
1642 def clear_disabled_fields
1643 def clear_disabled_fields
1643 if tracker
1644 if tracker
1644 tracker.disabled_core_fields.each do |attribute|
1645 tracker.disabled_core_fields.each do |attribute|
1645 send "#{attribute}=", nil
1646 send "#{attribute}=", nil
1646 end
1647 end
1647 self.done_ratio ||= 0
1648 self.done_ratio ||= 0
1648 end
1649 end
1649 end
1650 end
1650 end
1651 end
@@ -1,2727 +1,2745
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_close_duplicates_with_private_notes
1230 issue = Issue.generate!
1231 duplicate = Issue.generate!
1232 IssueRelation.create!(:issue_from => duplicate, :issue_to => issue,
1233 :relation_type => IssueRelation::TYPE_DUPLICATES)
1234 assert issue.reload.duplicates.include?(duplicate)
1235
1236 # Closing issue with private notes
1237 issue.init_journal(User.first, "Private notes")
1238 issue.private_notes = true
1239 issue.status = IssueStatus.where(:is_closed => true).first
1240 assert_save issue
1241
1242 duplicate.reload
1243 assert journal = duplicate.journals.detect {|journal| journal.notes == "Private notes"}
1244 assert_equal true, journal.private_notes
1245 end
1246
1229 def test_should_not_close_duplicated_issue
1247 def test_should_not_close_duplicated_issue
1230 issue1 = Issue.generate!
1248 issue1 = Issue.generate!
1231 issue2 = Issue.generate!
1249 issue2 = Issue.generate!
1232
1250
1233 # 2 is a dupe of 1
1251 # 2 is a dupe of 1
1234 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1252 IssueRelation.create(:issue_from => issue2, :issue_to => issue1,
1235 :relation_type => IssueRelation::TYPE_DUPLICATES)
1253 :relation_type => IssueRelation::TYPE_DUPLICATES)
1236 # 2 is a dup of 1 but 1 is not a duplicate of 2
1254 # 2 is a dup of 1 but 1 is not a duplicate of 2
1237 assert !issue2.reload.duplicates.include?(issue1)
1255 assert !issue2.reload.duplicates.include?(issue1)
1238
1256
1239 # Closing issue 2
1257 # Closing issue 2
1240 issue2.init_journal(User.first, "Closing issue2")
1258 issue2.init_journal(User.first, "Closing issue2")
1241 issue2.status = IssueStatus.where(:is_closed => true).first
1259 issue2.status = IssueStatus.where(:is_closed => true).first
1242 assert issue2.save
1260 assert issue2.save
1243 # 1 should not be also closed
1261 # 1 should not be also closed
1244 assert !issue1.reload.closed?
1262 assert !issue1.reload.closed?
1245 end
1263 end
1246
1264
1247 def test_assignable_versions
1265 def test_assignable_versions
1248 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1266 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1249 :status_id => 1, :fixed_version_id => 1,
1267 :status_id => 1, :fixed_version_id => 1,
1250 :subject => 'New issue')
1268 :subject => 'New issue')
1251 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1269 assert_equal ['open'], issue.assignable_versions.collect(&:status).uniq
1252 end
1270 end
1253
1271
1254 def test_should_not_be_able_to_assign_a_new_issue_to_a_closed_version
1272 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,
1273 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1256 :status_id => 1, :fixed_version_id => 1,
1274 :status_id => 1, :fixed_version_id => 1,
1257 :subject => 'New issue')
1275 :subject => 'New issue')
1258 assert !issue.save
1276 assert !issue.save
1259 assert_not_equal [], issue.errors[:fixed_version_id]
1277 assert_not_equal [], issue.errors[:fixed_version_id]
1260 end
1278 end
1261
1279
1262 def test_should_not_be_able_to_assign_a_new_issue_to_a_locked_version
1280 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,
1281 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1264 :status_id => 1, :fixed_version_id => 2,
1282 :status_id => 1, :fixed_version_id => 2,
1265 :subject => 'New issue')
1283 :subject => 'New issue')
1266 assert !issue.save
1284 assert !issue.save
1267 assert_not_equal [], issue.errors[:fixed_version_id]
1285 assert_not_equal [], issue.errors[:fixed_version_id]
1268 end
1286 end
1269
1287
1270 def test_should_be_able_to_assign_a_new_issue_to_an_open_version
1288 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,
1289 issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1,
1272 :status_id => 1, :fixed_version_id => 3,
1290 :status_id => 1, :fixed_version_id => 3,
1273 :subject => 'New issue')
1291 :subject => 'New issue')
1274 assert issue.save
1292 assert issue.save
1275 end
1293 end
1276
1294
1277 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1295 def test_should_be_able_to_update_an_issue_assigned_to_a_closed_version
1278 issue = Issue.find(11)
1296 issue = Issue.find(11)
1279 assert_equal 'closed', issue.fixed_version.status
1297 assert_equal 'closed', issue.fixed_version.status
1280 issue.subject = 'Subject changed'
1298 issue.subject = 'Subject changed'
1281 assert issue.save
1299 assert issue.save
1282 end
1300 end
1283
1301
1284 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1302 def test_should_not_be_able_to_reopen_an_issue_assigned_to_a_closed_version
1285 issue = Issue.find(11)
1303 issue = Issue.find(11)
1286 issue.status_id = 1
1304 issue.status_id = 1
1287 assert !issue.save
1305 assert !issue.save
1288 assert_not_equal [], issue.errors[:base]
1306 assert_not_equal [], issue.errors[:base]
1289 end
1307 end
1290
1308
1291 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1309 def test_should_be_able_to_reopen_and_reassign_an_issue_assigned_to_a_closed_version
1292 issue = Issue.find(11)
1310 issue = Issue.find(11)
1293 issue.status_id = 1
1311 issue.status_id = 1
1294 issue.fixed_version_id = 3
1312 issue.fixed_version_id = 3
1295 assert issue.save
1313 assert issue.save
1296 end
1314 end
1297
1315
1298 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1316 def test_should_be_able_to_reopen_an_issue_assigned_to_a_locked_version
1299 issue = Issue.find(12)
1317 issue = Issue.find(12)
1300 assert_equal 'locked', issue.fixed_version.status
1318 assert_equal 'locked', issue.fixed_version.status
1301 issue.status_id = 1
1319 issue.status_id = 1
1302 assert issue.save
1320 assert issue.save
1303 end
1321 end
1304
1322
1305 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1323 def test_should_not_be_able_to_keep_unshared_version_when_changing_project
1306 issue = Issue.find(2)
1324 issue = Issue.find(2)
1307 assert_equal 2, issue.fixed_version_id
1325 assert_equal 2, issue.fixed_version_id
1308 issue.project_id = 3
1326 issue.project_id = 3
1309 assert_nil issue.fixed_version_id
1327 assert_nil issue.fixed_version_id
1310 issue.fixed_version_id = 2
1328 issue.fixed_version_id = 2
1311 assert !issue.save
1329 assert !issue.save
1312 assert_include 'Target version is not included in the list', issue.errors.full_messages
1330 assert_include 'Target version is not included in the list', issue.errors.full_messages
1313 end
1331 end
1314
1332
1315 def test_should_keep_shared_version_when_changing_project
1333 def test_should_keep_shared_version_when_changing_project
1316 Version.find(2).update_attribute :sharing, 'tree'
1334 Version.find(2).update_attribute :sharing, 'tree'
1317
1335
1318 issue = Issue.find(2)
1336 issue = Issue.find(2)
1319 assert_equal 2, issue.fixed_version_id
1337 assert_equal 2, issue.fixed_version_id
1320 issue.project_id = 3
1338 issue.project_id = 3
1321 assert_equal 2, issue.fixed_version_id
1339 assert_equal 2, issue.fixed_version_id
1322 assert issue.save
1340 assert issue.save
1323 end
1341 end
1324
1342
1325 def test_allowed_target_projects_should_include_projects_with_issue_tracking_enabled
1343 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))
1344 assert_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1327 end
1345 end
1328
1346
1329 def test_allowed_target_projects_should_not_include_projects_with_issue_tracking_disabled
1347 def test_allowed_target_projects_should_not_include_projects_with_issue_tracking_disabled
1330 Project.find(2).disable_module! :issue_tracking
1348 Project.find(2).disable_module! :issue_tracking
1331 assert_not_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1349 assert_not_include Project.find(2), Issue.allowed_target_projects(User.find(2))
1332 end
1350 end
1333
1351
1334 def test_move_to_another_project_with_same_category
1352 def test_move_to_another_project_with_same_category
1335 issue = Issue.find(1)
1353 issue = Issue.find(1)
1336 issue.project = Project.find(2)
1354 issue.project = Project.find(2)
1337 assert issue.save
1355 assert issue.save
1338 issue.reload
1356 issue.reload
1339 assert_equal 2, issue.project_id
1357 assert_equal 2, issue.project_id
1340 # Category changes
1358 # Category changes
1341 assert_equal 4, issue.category_id
1359 assert_equal 4, issue.category_id
1342 # Make sure time entries were move to the target project
1360 # Make sure time entries were move to the target project
1343 assert_equal 2, issue.time_entries.first.project_id
1361 assert_equal 2, issue.time_entries.first.project_id
1344 end
1362 end
1345
1363
1346 def test_move_to_another_project_without_same_category
1364 def test_move_to_another_project_without_same_category
1347 issue = Issue.find(2)
1365 issue = Issue.find(2)
1348 issue.project = Project.find(2)
1366 issue.project = Project.find(2)
1349 assert issue.save
1367 assert issue.save
1350 issue.reload
1368 issue.reload
1351 assert_equal 2, issue.project_id
1369 assert_equal 2, issue.project_id
1352 # Category cleared
1370 # Category cleared
1353 assert_nil issue.category_id
1371 assert_nil issue.category_id
1354 end
1372 end
1355
1373
1356 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1374 def test_move_to_another_project_should_clear_fixed_version_when_not_shared
1357 issue = Issue.find(1)
1375 issue = Issue.find(1)
1358 issue.update_attribute(:fixed_version_id, 1)
1376 issue.update_attribute(:fixed_version_id, 1)
1359 issue.project = Project.find(2)
1377 issue.project = Project.find(2)
1360 assert issue.save
1378 assert issue.save
1361 issue.reload
1379 issue.reload
1362 assert_equal 2, issue.project_id
1380 assert_equal 2, issue.project_id
1363 # Cleared fixed_version
1381 # Cleared fixed_version
1364 assert_equal nil, issue.fixed_version
1382 assert_equal nil, issue.fixed_version
1365 end
1383 end
1366
1384
1367 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1385 def test_move_to_another_project_should_keep_fixed_version_when_shared_with_the_target_project
1368 issue = Issue.find(1)
1386 issue = Issue.find(1)
1369 issue.update_attribute(:fixed_version_id, 4)
1387 issue.update_attribute(:fixed_version_id, 4)
1370 issue.project = Project.find(5)
1388 issue.project = Project.find(5)
1371 assert issue.save
1389 assert issue.save
1372 issue.reload
1390 issue.reload
1373 assert_equal 5, issue.project_id
1391 assert_equal 5, issue.project_id
1374 # Keep fixed_version
1392 # Keep fixed_version
1375 assert_equal 4, issue.fixed_version_id
1393 assert_equal 4, issue.fixed_version_id
1376 end
1394 end
1377
1395
1378 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1396 def test_move_to_another_project_should_clear_fixed_version_when_not_shared_with_the_target_project
1379 issue = Issue.find(1)
1397 issue = Issue.find(1)
1380 issue.update_attribute(:fixed_version_id, 1)
1398 issue.update_attribute(:fixed_version_id, 1)
1381 issue.project = Project.find(5)
1399 issue.project = Project.find(5)
1382 assert issue.save
1400 assert issue.save
1383 issue.reload
1401 issue.reload
1384 assert_equal 5, issue.project_id
1402 assert_equal 5, issue.project_id
1385 # Cleared fixed_version
1403 # Cleared fixed_version
1386 assert_equal nil, issue.fixed_version
1404 assert_equal nil, issue.fixed_version
1387 end
1405 end
1388
1406
1389 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1407 def test_move_to_another_project_should_keep_fixed_version_when_shared_systemwide
1390 issue = Issue.find(1)
1408 issue = Issue.find(1)
1391 issue.update_attribute(:fixed_version_id, 7)
1409 issue.update_attribute(:fixed_version_id, 7)
1392 issue.project = Project.find(2)
1410 issue.project = Project.find(2)
1393 assert issue.save
1411 assert issue.save
1394 issue.reload
1412 issue.reload
1395 assert_equal 2, issue.project_id
1413 assert_equal 2, issue.project_id
1396 # Keep fixed_version
1414 # Keep fixed_version
1397 assert_equal 7, issue.fixed_version_id
1415 assert_equal 7, issue.fixed_version_id
1398 end
1416 end
1399
1417
1400 def test_move_to_another_project_should_keep_parent_if_valid
1418 def test_move_to_another_project_should_keep_parent_if_valid
1401 issue = Issue.find(1)
1419 issue = Issue.find(1)
1402 issue.update_attribute(:parent_issue_id, 2)
1420 issue.update_attribute(:parent_issue_id, 2)
1403 issue.project = Project.find(3)
1421 issue.project = Project.find(3)
1404 assert issue.save
1422 assert issue.save
1405 issue.reload
1423 issue.reload
1406 assert_equal 2, issue.parent_id
1424 assert_equal 2, issue.parent_id
1407 end
1425 end
1408
1426
1409 def test_move_to_another_project_should_clear_parent_if_not_valid
1427 def test_move_to_another_project_should_clear_parent_if_not_valid
1410 issue = Issue.find(1)
1428 issue = Issue.find(1)
1411 issue.update_attribute(:parent_issue_id, 2)
1429 issue.update_attribute(:parent_issue_id, 2)
1412 issue.project = Project.find(2)
1430 issue.project = Project.find(2)
1413 assert issue.save
1431 assert issue.save
1414 issue.reload
1432 issue.reload
1415 assert_nil issue.parent_id
1433 assert_nil issue.parent_id
1416 end
1434 end
1417
1435
1418 def test_move_to_another_project_with_disabled_tracker
1436 def test_move_to_another_project_with_disabled_tracker
1419 issue = Issue.find(1)
1437 issue = Issue.find(1)
1420 target = Project.find(2)
1438 target = Project.find(2)
1421 target.tracker_ids = [3]
1439 target.tracker_ids = [3]
1422 target.save
1440 target.save
1423 issue.project = target
1441 issue.project = target
1424 assert issue.save
1442 assert issue.save
1425 issue.reload
1443 issue.reload
1426 assert_equal 2, issue.project_id
1444 assert_equal 2, issue.project_id
1427 assert_equal 3, issue.tracker_id
1445 assert_equal 3, issue.tracker_id
1428 end
1446 end
1429
1447
1430 def test_copy_to_the_same_project
1448 def test_copy_to_the_same_project
1431 issue = Issue.find(1)
1449 issue = Issue.find(1)
1432 copy = issue.copy
1450 copy = issue.copy
1433 assert_difference 'Issue.count' do
1451 assert_difference 'Issue.count' do
1434 copy.save!
1452 copy.save!
1435 end
1453 end
1436 assert_kind_of Issue, copy
1454 assert_kind_of Issue, copy
1437 assert_equal issue.project, copy.project
1455 assert_equal issue.project, copy.project
1438 assert_equal "125", copy.custom_value_for(2).value
1456 assert_equal "125", copy.custom_value_for(2).value
1439 end
1457 end
1440
1458
1441 def test_copy_to_another_project_and_tracker
1459 def test_copy_to_another_project_and_tracker
1442 issue = Issue.find(1)
1460 issue = Issue.find(1)
1443 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1461 copy = issue.copy(:project_id => 3, :tracker_id => 2)
1444 assert_difference 'Issue.count' do
1462 assert_difference 'Issue.count' do
1445 copy.save!
1463 copy.save!
1446 end
1464 end
1447 copy.reload
1465 copy.reload
1448 assert_kind_of Issue, copy
1466 assert_kind_of Issue, copy
1449 assert_equal Project.find(3), copy.project
1467 assert_equal Project.find(3), copy.project
1450 assert_equal Tracker.find(2), copy.tracker
1468 assert_equal Tracker.find(2), copy.tracker
1451 # Custom field #2 is not associated with target tracker
1469 # Custom field #2 is not associated with target tracker
1452 assert_nil copy.custom_value_for(2)
1470 assert_nil copy.custom_value_for(2)
1453 end
1471 end
1454
1472
1455 test "#copy should not create a journal" do
1473 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)
1474 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :assigned_to_id => 3}, :link => false)
1457 copy.save!
1475 copy.save!
1458 assert_equal 0, copy.reload.journals.size
1476 assert_equal 0, copy.reload.journals.size
1459 end
1477 end
1460
1478
1461 test "#copy should allow assigned_to changes" do
1479 test "#copy should allow assigned_to changes" do
1462 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1480 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :assigned_to_id => 3)
1463 assert_equal 3, copy.assigned_to_id
1481 assert_equal 3, copy.assigned_to_id
1464 end
1482 end
1465
1483
1466 test "#copy should allow status changes" do
1484 test "#copy should allow status changes" do
1467 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1485 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :status_id => 2)
1468 assert_equal 2, copy.status_id
1486 assert_equal 2, copy.status_id
1469 end
1487 end
1470
1488
1471 test "#copy should allow start date changes" do
1489 test "#copy should allow start date changes" do
1472 date = Date.today
1490 date = Date.today
1473 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1491 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :start_date => date)
1474 assert_equal date, copy.start_date
1492 assert_equal date, copy.start_date
1475 end
1493 end
1476
1494
1477 test "#copy should allow due date changes" do
1495 test "#copy should allow due date changes" do
1478 date = Date.today
1496 date = Date.today
1479 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1497 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2, :due_date => date)
1480 assert_equal date, copy.due_date
1498 assert_equal date, copy.due_date
1481 end
1499 end
1482
1500
1483 test "#copy should set current user as author" do
1501 test "#copy should set current user as author" do
1484 User.current = User.find(9)
1502 User.current = User.find(9)
1485 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1503 copy = Issue.find(1).copy(:project_id => 3, :tracker_id => 2)
1486 assert_equal User.current, copy.author
1504 assert_equal User.current, copy.author
1487 end
1505 end
1488
1506
1489 test "#copy should create a journal with notes" do
1507 test "#copy should create a journal with notes" do
1490 date = Date.today
1508 date = Date.today
1491 notes = "Notes added when copying"
1509 notes = "Notes added when copying"
1492 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :start_date => date}, :link => false)
1510 copy = Issue.find(1).copy({:project_id => 3, :tracker_id => 2, :start_date => date}, :link => false)
1493 copy.init_journal(User.current, notes)
1511 copy.init_journal(User.current, notes)
1494 copy.save!
1512 copy.save!
1495
1513
1496 assert_equal 1, copy.journals.size
1514 assert_equal 1, copy.journals.size
1497 journal = copy.journals.first
1515 journal = copy.journals.first
1498 assert_equal 0, journal.details.size
1516 assert_equal 0, journal.details.size
1499 assert_equal notes, journal.notes
1517 assert_equal notes, journal.notes
1500 end
1518 end
1501
1519
1502 def test_valid_parent_project
1520 def test_valid_parent_project
1503 issue = Issue.find(1)
1521 issue = Issue.find(1)
1504 issue_in_same_project = Issue.find(2)
1522 issue_in_same_project = Issue.find(2)
1505 issue_in_child_project = Issue.find(5)
1523 issue_in_child_project = Issue.find(5)
1506 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1524 issue_in_grandchild_project = Issue.generate!(:project_id => 6, :tracker_id => 1)
1507 issue_in_other_child_project = Issue.find(6)
1525 issue_in_other_child_project = Issue.find(6)
1508 issue_in_different_tree = Issue.find(4)
1526 issue_in_different_tree = Issue.find(4)
1509
1527
1510 with_settings :cross_project_subtasks => '' do
1528 with_settings :cross_project_subtasks => '' do
1511 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1529 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1512 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1530 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1513 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1531 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1514 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1532 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1515 end
1533 end
1516
1534
1517 with_settings :cross_project_subtasks => 'system' do
1535 with_settings :cross_project_subtasks => 'system' do
1518 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1536 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1519 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1537 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1520 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1538 assert_equal true, issue.valid_parent_project?(issue_in_different_tree)
1521 end
1539 end
1522
1540
1523 with_settings :cross_project_subtasks => 'tree' do
1541 with_settings :cross_project_subtasks => 'tree' do
1524 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1542 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1525 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1543 assert_equal true, issue.valid_parent_project?(issue_in_child_project)
1526 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1544 assert_equal true, issue.valid_parent_project?(issue_in_grandchild_project)
1527 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1545 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1528
1546
1529 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_same_project)
1547 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)
1548 assert_equal true, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1531 end
1549 end
1532
1550
1533 with_settings :cross_project_subtasks => 'descendants' do
1551 with_settings :cross_project_subtasks => 'descendants' do
1534 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1552 assert_equal true, issue.valid_parent_project?(issue_in_same_project)
1535 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1553 assert_equal false, issue.valid_parent_project?(issue_in_child_project)
1536 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1554 assert_equal false, issue.valid_parent_project?(issue_in_grandchild_project)
1537 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1555 assert_equal false, issue.valid_parent_project?(issue_in_different_tree)
1538
1556
1539 assert_equal true, issue_in_child_project.valid_parent_project?(issue)
1557 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)
1558 assert_equal false, issue_in_child_project.valid_parent_project?(issue_in_other_child_project)
1541 end
1559 end
1542 end
1560 end
1543
1561
1544 def test_recipients_should_include_previous_assignee
1562 def test_recipients_should_include_previous_assignee
1545 user = User.find(3)
1563 user = User.find(3)
1546 user.members.update_all ["mail_notification = ?", false]
1564 user.members.update_all ["mail_notification = ?", false]
1547 user.update_attribute :mail_notification, 'only_assigned'
1565 user.update_attribute :mail_notification, 'only_assigned'
1548
1566
1549 issue = Issue.find(2)
1567 issue = Issue.find(2)
1550 issue.assigned_to = nil
1568 issue.assigned_to = nil
1551 assert_include user.mail, issue.recipients
1569 assert_include user.mail, issue.recipients
1552 issue.save!
1570 issue.save!
1553 assert !issue.recipients.include?(user.mail)
1571 assert !issue.recipients.include?(user.mail)
1554 end
1572 end
1555
1573
1556 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1574 def test_recipients_should_not_include_users_that_cannot_view_the_issue
1557 issue = Issue.find(12)
1575 issue = Issue.find(12)
1558 assert issue.recipients.include?(issue.author.mail)
1576 assert issue.recipients.include?(issue.author.mail)
1559 # copy the issue to a private project
1577 # copy the issue to a private project
1560 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1578 copy = issue.copy(:project_id => 5, :tracker_id => 2)
1561 # author is not a member of project anymore
1579 # author is not a member of project anymore
1562 assert !copy.recipients.include?(copy.author.mail)
1580 assert !copy.recipients.include?(copy.author.mail)
1563 end
1581 end
1564
1582
1565 def test_recipients_should_include_the_assigned_group_members
1583 def test_recipients_should_include_the_assigned_group_members
1566 group_member = User.generate!
1584 group_member = User.generate!
1567 group = Group.generate!
1585 group = Group.generate!
1568 group.users << group_member
1586 group.users << group_member
1569
1587
1570 issue = Issue.find(12)
1588 issue = Issue.find(12)
1571 issue.assigned_to = group
1589 issue.assigned_to = group
1572 assert issue.recipients.include?(group_member.mail)
1590 assert issue.recipients.include?(group_member.mail)
1573 end
1591 end
1574
1592
1575 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1593 def test_watcher_recipients_should_not_include_users_that_cannot_view_the_issue
1576 user = User.find(3)
1594 user = User.find(3)
1577 issue = Issue.find(9)
1595 issue = Issue.find(9)
1578 Watcher.create!(:user => user, :watchable => issue)
1596 Watcher.create!(:user => user, :watchable => issue)
1579 assert issue.watched_by?(user)
1597 assert issue.watched_by?(user)
1580 assert !issue.watcher_recipients.include?(user.mail)
1598 assert !issue.watcher_recipients.include?(user.mail)
1581 end
1599 end
1582
1600
1583 def test_issue_destroy
1601 def test_issue_destroy
1584 Issue.find(1).destroy
1602 Issue.find(1).destroy
1585 assert_nil Issue.find_by_id(1)
1603 assert_nil Issue.find_by_id(1)
1586 assert_nil TimeEntry.find_by_issue_id(1)
1604 assert_nil TimeEntry.find_by_issue_id(1)
1587 end
1605 end
1588
1606
1589 def test_destroy_should_delete_time_entries_custom_values
1607 def test_destroy_should_delete_time_entries_custom_values
1590 issue = Issue.generate!
1608 issue = Issue.generate!
1591 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1609 time_entry = TimeEntry.generate!(:issue => issue, :custom_field_values => {10 => '1'})
1592
1610
1593 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1611 assert_difference 'CustomValue.where(:customized_type => "TimeEntry").count', -1 do
1594 assert issue.destroy
1612 assert issue.destroy
1595 end
1613 end
1596 end
1614 end
1597
1615
1598 def test_destroying_a_deleted_issue_should_not_raise_an_error
1616 def test_destroying_a_deleted_issue_should_not_raise_an_error
1599 issue = Issue.find(1)
1617 issue = Issue.find(1)
1600 Issue.find(1).destroy
1618 Issue.find(1).destroy
1601
1619
1602 assert_nothing_raised do
1620 assert_nothing_raised do
1603 assert_no_difference 'Issue.count' do
1621 assert_no_difference 'Issue.count' do
1604 issue.destroy
1622 issue.destroy
1605 end
1623 end
1606 assert issue.destroyed?
1624 assert issue.destroyed?
1607 end
1625 end
1608 end
1626 end
1609
1627
1610 def test_destroying_a_stale_issue_should_not_raise_an_error
1628 def test_destroying_a_stale_issue_should_not_raise_an_error
1611 issue = Issue.find(1)
1629 issue = Issue.find(1)
1612 Issue.find(1).update_attribute :subject, "Updated"
1630 Issue.find(1).update_attribute :subject, "Updated"
1613
1631
1614 assert_nothing_raised do
1632 assert_nothing_raised do
1615 assert_difference 'Issue.count', -1 do
1633 assert_difference 'Issue.count', -1 do
1616 issue.destroy
1634 issue.destroy
1617 end
1635 end
1618 assert issue.destroyed?
1636 assert issue.destroyed?
1619 end
1637 end
1620 end
1638 end
1621
1639
1622 def test_blocked
1640 def test_blocked
1623 blocked_issue = Issue.find(9)
1641 blocked_issue = Issue.find(9)
1624 blocking_issue = Issue.find(10)
1642 blocking_issue = Issue.find(10)
1625
1643
1626 assert blocked_issue.blocked?
1644 assert blocked_issue.blocked?
1627 assert !blocking_issue.blocked?
1645 assert !blocking_issue.blocked?
1628 end
1646 end
1629
1647
1630 def test_blocked_issues_dont_allow_closed_statuses
1648 def test_blocked_issues_dont_allow_closed_statuses
1631 blocked_issue = Issue.find(9)
1649 blocked_issue = Issue.find(9)
1632
1650
1633 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1651 allowed_statuses = blocked_issue.new_statuses_allowed_to(users(:users_002))
1634 assert !allowed_statuses.empty?
1652 assert !allowed_statuses.empty?
1635 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1653 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1636 assert closed_statuses.empty?
1654 assert closed_statuses.empty?
1637 end
1655 end
1638
1656
1639 def test_unblocked_issues_allow_closed_statuses
1657 def test_unblocked_issues_allow_closed_statuses
1640 blocking_issue = Issue.find(10)
1658 blocking_issue = Issue.find(10)
1641
1659
1642 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1660 allowed_statuses = blocking_issue.new_statuses_allowed_to(users(:users_002))
1643 assert !allowed_statuses.empty?
1661 assert !allowed_statuses.empty?
1644 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1662 closed_statuses = allowed_statuses.select {|st| st.is_closed?}
1645 assert !closed_statuses.empty?
1663 assert !closed_statuses.empty?
1646 end
1664 end
1647
1665
1648 def test_reschedule_an_issue_without_dates
1666 def test_reschedule_an_issue_without_dates
1649 with_settings :non_working_week_days => [] do
1667 with_settings :non_working_week_days => [] do
1650 issue = Issue.new(:start_date => nil, :due_date => nil)
1668 issue = Issue.new(:start_date => nil, :due_date => nil)
1651 issue.reschedule_on '2012-10-09'.to_date
1669 issue.reschedule_on '2012-10-09'.to_date
1652 assert_equal '2012-10-09'.to_date, issue.start_date
1670 assert_equal '2012-10-09'.to_date, issue.start_date
1653 assert_equal '2012-10-09'.to_date, issue.due_date
1671 assert_equal '2012-10-09'.to_date, issue.due_date
1654 end
1672 end
1655
1673
1656 with_settings :non_working_week_days => %w(6 7) do
1674 with_settings :non_working_week_days => %w(6 7) do
1657 issue = Issue.new(:start_date => nil, :due_date => nil)
1675 issue = Issue.new(:start_date => nil, :due_date => nil)
1658 issue.reschedule_on '2012-10-09'.to_date
1676 issue.reschedule_on '2012-10-09'.to_date
1659 assert_equal '2012-10-09'.to_date, issue.start_date
1677 assert_equal '2012-10-09'.to_date, issue.start_date
1660 assert_equal '2012-10-09'.to_date, issue.due_date
1678 assert_equal '2012-10-09'.to_date, issue.due_date
1661
1679
1662 issue = Issue.new(:start_date => nil, :due_date => nil)
1680 issue = Issue.new(:start_date => nil, :due_date => nil)
1663 issue.reschedule_on '2012-10-13'.to_date
1681 issue.reschedule_on '2012-10-13'.to_date
1664 assert_equal '2012-10-15'.to_date, issue.start_date
1682 assert_equal '2012-10-15'.to_date, issue.start_date
1665 assert_equal '2012-10-15'.to_date, issue.due_date
1683 assert_equal '2012-10-15'.to_date, issue.due_date
1666 end
1684 end
1667 end
1685 end
1668
1686
1669 def test_reschedule_an_issue_with_start_date
1687 def test_reschedule_an_issue_with_start_date
1670 with_settings :non_working_week_days => [] do
1688 with_settings :non_working_week_days => [] do
1671 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1689 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1672 issue.reschedule_on '2012-10-13'.to_date
1690 issue.reschedule_on '2012-10-13'.to_date
1673 assert_equal '2012-10-13'.to_date, issue.start_date
1691 assert_equal '2012-10-13'.to_date, issue.start_date
1674 assert_equal '2012-10-13'.to_date, issue.due_date
1692 assert_equal '2012-10-13'.to_date, issue.due_date
1675 end
1693 end
1676
1694
1677 with_settings :non_working_week_days => %w(6 7) do
1695 with_settings :non_working_week_days => %w(6 7) do
1678 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1696 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1679 issue.reschedule_on '2012-10-11'.to_date
1697 issue.reschedule_on '2012-10-11'.to_date
1680 assert_equal '2012-10-11'.to_date, issue.start_date
1698 assert_equal '2012-10-11'.to_date, issue.start_date
1681 assert_equal '2012-10-11'.to_date, issue.due_date
1699 assert_equal '2012-10-11'.to_date, issue.due_date
1682
1700
1683 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1701 issue = Issue.new(:start_date => '2012-10-09', :due_date => nil)
1684 issue.reschedule_on '2012-10-13'.to_date
1702 issue.reschedule_on '2012-10-13'.to_date
1685 assert_equal '2012-10-15'.to_date, issue.start_date
1703 assert_equal '2012-10-15'.to_date, issue.start_date
1686 assert_equal '2012-10-15'.to_date, issue.due_date
1704 assert_equal '2012-10-15'.to_date, issue.due_date
1687 end
1705 end
1688 end
1706 end
1689
1707
1690 def test_reschedule_an_issue_with_start_and_due_dates
1708 def test_reschedule_an_issue_with_start_and_due_dates
1691 with_settings :non_working_week_days => [] do
1709 with_settings :non_working_week_days => [] do
1692 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1710 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-15')
1693 issue.reschedule_on '2012-10-13'.to_date
1711 issue.reschedule_on '2012-10-13'.to_date
1694 assert_equal '2012-10-13'.to_date, issue.start_date
1712 assert_equal '2012-10-13'.to_date, issue.start_date
1695 assert_equal '2012-10-19'.to_date, issue.due_date
1713 assert_equal '2012-10-19'.to_date, issue.due_date
1696 end
1714 end
1697
1715
1698 with_settings :non_working_week_days => %w(6 7) do
1716 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
1717 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
1718 issue.reschedule_on '2012-10-11'.to_date
1701 assert_equal '2012-10-11'.to_date, issue.start_date
1719 assert_equal '2012-10-11'.to_date, issue.start_date
1702 assert_equal '2012-10-23'.to_date, issue.due_date
1720 assert_equal '2012-10-23'.to_date, issue.due_date
1703
1721
1704 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1722 issue = Issue.new(:start_date => '2012-10-09', :due_date => '2012-10-19')
1705 issue.reschedule_on '2012-10-13'.to_date
1723 issue.reschedule_on '2012-10-13'.to_date
1706 assert_equal '2012-10-15'.to_date, issue.start_date
1724 assert_equal '2012-10-15'.to_date, issue.start_date
1707 assert_equal '2012-10-25'.to_date, issue.due_date
1725 assert_equal '2012-10-25'.to_date, issue.due_date
1708 end
1726 end
1709 end
1727 end
1710
1728
1711 def test_rescheduling_an_issue_to_a_later_due_date_should_reschedule_following_issue
1729 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')
1730 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')
1731 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1714 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1732 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1715 :relation_type => IssueRelation::TYPE_PRECEDES)
1733 :relation_type => IssueRelation::TYPE_PRECEDES)
1716 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1734 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1717
1735
1718 issue1.reload
1736 issue1.reload
1719 issue1.due_date = '2012-10-23'
1737 issue1.due_date = '2012-10-23'
1720 issue1.save!
1738 issue1.save!
1721 issue2.reload
1739 issue2.reload
1722 assert_equal Date.parse('2012-10-24'), issue2.start_date
1740 assert_equal Date.parse('2012-10-24'), issue2.start_date
1723 assert_equal Date.parse('2012-10-26'), issue2.due_date
1741 assert_equal Date.parse('2012-10-26'), issue2.due_date
1724 end
1742 end
1725
1743
1726 def test_rescheduling_an_issue_to_an_earlier_due_date_should_reschedule_following_issue
1744 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')
1745 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')
1746 issue2 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1729 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1747 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1730 :relation_type => IssueRelation::TYPE_PRECEDES)
1748 :relation_type => IssueRelation::TYPE_PRECEDES)
1731 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1749 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1732
1750
1733 issue1.reload
1751 issue1.reload
1734 issue1.start_date = '2012-09-17'
1752 issue1.start_date = '2012-09-17'
1735 issue1.due_date = '2012-09-18'
1753 issue1.due_date = '2012-09-18'
1736 issue1.save!
1754 issue1.save!
1737 issue2.reload
1755 issue2.reload
1738 assert_equal Date.parse('2012-09-19'), issue2.start_date
1756 assert_equal Date.parse('2012-09-19'), issue2.start_date
1739 assert_equal Date.parse('2012-09-21'), issue2.due_date
1757 assert_equal Date.parse('2012-09-21'), issue2.due_date
1740 end
1758 end
1741
1759
1742 def test_rescheduling_reschedule_following_issue_earlier_should_consider_other_preceding_issues
1760 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')
1761 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')
1762 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')
1763 issue3 = Issue.generate!(:start_date => '2012-10-01', :due_date => '2012-10-02')
1746 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1764 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1747 :relation_type => IssueRelation::TYPE_PRECEDES)
1765 :relation_type => IssueRelation::TYPE_PRECEDES)
1748 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1766 IssueRelation.create!(:issue_from => issue3, :issue_to => issue2,
1749 :relation_type => IssueRelation::TYPE_PRECEDES)
1767 :relation_type => IssueRelation::TYPE_PRECEDES)
1750 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1768 assert_equal Date.parse('2012-10-18'), issue2.reload.start_date
1751
1769
1752 issue1.reload
1770 issue1.reload
1753 issue1.start_date = '2012-09-17'
1771 issue1.start_date = '2012-09-17'
1754 issue1.due_date = '2012-09-18'
1772 issue1.due_date = '2012-09-18'
1755 issue1.save!
1773 issue1.save!
1756 issue2.reload
1774 issue2.reload
1757 # Issue 2 must start after Issue 3
1775 # Issue 2 must start after Issue 3
1758 assert_equal Date.parse('2012-10-03'), issue2.start_date
1776 assert_equal Date.parse('2012-10-03'), issue2.start_date
1759 assert_equal Date.parse('2012-10-05'), issue2.due_date
1777 assert_equal Date.parse('2012-10-05'), issue2.due_date
1760 end
1778 end
1761
1779
1762 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1780 def test_rescheduling_a_stale_issue_should_not_raise_an_error
1763 with_settings :non_working_week_days => [] do
1781 with_settings :non_working_week_days => [] do
1764 stale = Issue.find(1)
1782 stale = Issue.find(1)
1765 issue = Issue.find(1)
1783 issue = Issue.find(1)
1766 issue.subject = "Updated"
1784 issue.subject = "Updated"
1767 issue.save!
1785 issue.save!
1768 date = 10.days.from_now.to_date
1786 date = 10.days.from_now.to_date
1769 assert_nothing_raised do
1787 assert_nothing_raised do
1770 stale.reschedule_on!(date)
1788 stale.reschedule_on!(date)
1771 end
1789 end
1772 assert_equal date, stale.reload.start_date
1790 assert_equal date, stale.reload.start_date
1773 end
1791 end
1774 end
1792 end
1775
1793
1776 def test_child_issue_should_consider_parent_soonest_start_on_create
1794 def test_child_issue_should_consider_parent_soonest_start_on_create
1777 set_language_if_valid 'en'
1795 set_language_if_valid 'en'
1778 issue1 = Issue.generate!(:start_date => '2012-10-15', :due_date => '2012-10-17')
1796 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')
1797 issue2 = Issue.generate!(:start_date => '2012-10-18', :due_date => '2012-10-20')
1780 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1798 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2,
1781 :relation_type => IssueRelation::TYPE_PRECEDES)
1799 :relation_type => IssueRelation::TYPE_PRECEDES)
1782 issue1.reload
1800 issue1.reload
1783 issue2.reload
1801 issue2.reload
1784 assert_equal Date.parse('2012-10-18'), issue2.start_date
1802 assert_equal Date.parse('2012-10-18'), issue2.start_date
1785
1803
1786 with_settings :date_format => '%m/%d/%Y' do
1804 with_settings :date_format => '%m/%d/%Y' do
1787 child = Issue.new(:parent_issue_id => issue2.id, :start_date => '2012-10-16',
1805 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)
1806 :project_id => 1, :tracker_id => 1, :status_id => 1, :subject => 'Child', :author_id => 1)
1789 assert !child.valid?
1807 assert !child.valid?
1790 assert_include 'Start date cannot be earlier than 10/18/2012 because of preceding issues', child.errors.full_messages
1808 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
1809 assert_equal Date.parse('2012-10-18'), child.soonest_start
1792 child.start_date = '2012-10-18'
1810 child.start_date = '2012-10-18'
1793 assert child.save
1811 assert child.save
1794 end
1812 end
1795 end
1813 end
1796
1814
1797 def test_setting_parent_to_a_dependent_issue_should_not_validate
1815 def test_setting_parent_to_a_dependent_issue_should_not_validate
1798 set_language_if_valid 'en'
1816 set_language_if_valid 'en'
1799 issue1 = Issue.generate!
1817 issue1 = Issue.generate!
1800 issue2 = Issue.generate!
1818 issue2 = Issue.generate!
1801 issue3 = Issue.generate!
1819 issue3 = Issue.generate!
1802 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1820 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)
1821 IssueRelation.create!(:issue_from => issue3, :issue_to => issue1, :relation_type => IssueRelation::TYPE_PRECEDES)
1804 issue3.reload
1822 issue3.reload
1805 issue3.parent_issue_id = issue2.id
1823 issue3.parent_issue_id = issue2.id
1806 assert !issue3.valid?
1824 assert !issue3.valid?
1807 assert_include 'Parent task is invalid', issue3.errors.full_messages
1825 assert_include 'Parent task is invalid', issue3.errors.full_messages
1808 end
1826 end
1809
1827
1810 def test_setting_parent_should_not_allow_circular_dependency
1828 def test_setting_parent_should_not_allow_circular_dependency
1811 set_language_if_valid 'en'
1829 set_language_if_valid 'en'
1812 issue1 = Issue.generate!
1830 issue1 = Issue.generate!
1813 issue2 = Issue.generate!
1831 issue2 = Issue.generate!
1814 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1832 IssueRelation.create!(:issue_from => issue1, :issue_to => issue2, :relation_type => IssueRelation::TYPE_PRECEDES)
1815 issue3 = Issue.generate!
1833 issue3 = Issue.generate!
1816 issue2.reload
1834 issue2.reload
1817 issue2.parent_issue_id = issue3.id
1835 issue2.parent_issue_id = issue3.id
1818 issue2.save!
1836 issue2.save!
1819 issue4 = Issue.generate!
1837 issue4 = Issue.generate!
1820 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
1838 IssueRelation.create!(:issue_from => issue3, :issue_to => issue4, :relation_type => IssueRelation::TYPE_PRECEDES)
1821 issue4.reload
1839 issue4.reload
1822 issue4.parent_issue_id = issue1.id
1840 issue4.parent_issue_id = issue1.id
1823 assert !issue4.valid?
1841 assert !issue4.valid?
1824 assert_include 'Parent task is invalid', issue4.errors.full_messages
1842 assert_include 'Parent task is invalid', issue4.errors.full_messages
1825 end
1843 end
1826
1844
1827 def test_overdue
1845 def test_overdue
1828 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1846 assert Issue.new(:due_date => 1.day.ago.to_date).overdue?
1829 assert !Issue.new(:due_date => Date.today).overdue?
1847 assert !Issue.new(:due_date => Date.today).overdue?
1830 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1848 assert !Issue.new(:due_date => 1.day.from_now.to_date).overdue?
1831 assert !Issue.new(:due_date => nil).overdue?
1849 assert !Issue.new(:due_date => nil).overdue?
1832 assert !Issue.new(:due_date => 1.day.ago.to_date,
1850 assert !Issue.new(:due_date => 1.day.ago.to_date,
1833 :status => IssueStatus.where(:is_closed => true).first
1851 :status => IssueStatus.where(:is_closed => true).first
1834 ).overdue?
1852 ).overdue?
1835 end
1853 end
1836
1854
1837 test "#behind_schedule? should be false if the issue has no start_date" do
1855 test "#behind_schedule? should be false if the issue has no start_date" do
1838 assert !Issue.new(:start_date => nil,
1856 assert !Issue.new(:start_date => nil,
1839 :due_date => 1.day.from_now.to_date,
1857 :due_date => 1.day.from_now.to_date,
1840 :done_ratio => 0).behind_schedule?
1858 :done_ratio => 0).behind_schedule?
1841 end
1859 end
1842
1860
1843 test "#behind_schedule? should be false if the issue has no end_date" do
1861 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,
1862 assert !Issue.new(:start_date => 1.day.from_now.to_date,
1845 :due_date => nil,
1863 :due_date => nil,
1846 :done_ratio => 0).behind_schedule?
1864 :done_ratio => 0).behind_schedule?
1847 end
1865 end
1848
1866
1849 test "#behind_schedule? should be false if the issue has more done than it's calendar time" do
1867 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,
1868 assert !Issue.new(:start_date => 50.days.ago.to_date,
1851 :due_date => 50.days.from_now.to_date,
1869 :due_date => 50.days.from_now.to_date,
1852 :done_ratio => 90).behind_schedule?
1870 :done_ratio => 90).behind_schedule?
1853 end
1871 end
1854
1872
1855 test "#behind_schedule? should be true if the issue hasn't been started at all" do
1873 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,
1874 assert Issue.new(:start_date => 1.day.ago.to_date,
1857 :due_date => 1.day.from_now.to_date,
1875 :due_date => 1.day.from_now.to_date,
1858 :done_ratio => 0).behind_schedule?
1876 :done_ratio => 0).behind_schedule?
1859 end
1877 end
1860
1878
1861 test "#behind_schedule? should be true if the issue has used more calendar time than it's done ratio" do
1879 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,
1880 assert Issue.new(:start_date => 100.days.ago.to_date,
1863 :due_date => Date.today,
1881 :due_date => Date.today,
1864 :done_ratio => 90).behind_schedule?
1882 :done_ratio => 90).behind_schedule?
1865 end
1883 end
1866
1884
1867 test "#assignable_users should be Users" do
1885 test "#assignable_users should be Users" do
1868 assert_kind_of User, Issue.find(1).assignable_users.first
1886 assert_kind_of User, Issue.find(1).assignable_users.first
1869 end
1887 end
1870
1888
1871 test "#assignable_users should include the issue author" do
1889 test "#assignable_users should include the issue author" do
1872 non_project_member = User.generate!
1890 non_project_member = User.generate!
1873 issue = Issue.generate!(:author => non_project_member)
1891 issue = Issue.generate!(:author => non_project_member)
1874
1892
1875 assert issue.assignable_users.include?(non_project_member)
1893 assert issue.assignable_users.include?(non_project_member)
1876 end
1894 end
1877
1895
1878 def test_assignable_users_should_not_include_anonymous_user
1896 def test_assignable_users_should_not_include_anonymous_user
1879 issue = Issue.generate!(:author => User.anonymous)
1897 issue = Issue.generate!(:author => User.anonymous)
1880
1898
1881 assert !issue.assignable_users.include?(User.anonymous)
1899 assert !issue.assignable_users.include?(User.anonymous)
1882 end
1900 end
1883
1901
1884 def test_assignable_users_should_not_include_locked_user
1902 def test_assignable_users_should_not_include_locked_user
1885 user = User.generate!
1903 user = User.generate!
1886 issue = Issue.generate!(:author => user)
1904 issue = Issue.generate!(:author => user)
1887 user.lock!
1905 user.lock!
1888
1906
1889 assert !issue.assignable_users.include?(user)
1907 assert !issue.assignable_users.include?(user)
1890 end
1908 end
1891
1909
1892 test "#assignable_users should include the current assignee" do
1910 test "#assignable_users should include the current assignee" do
1893 user = User.generate!
1911 user = User.generate!
1894 issue = Issue.generate!(:assigned_to => user)
1912 issue = Issue.generate!(:assigned_to => user)
1895 user.lock!
1913 user.lock!
1896
1914
1897 assert Issue.find(issue.id).assignable_users.include?(user)
1915 assert Issue.find(issue.id).assignable_users.include?(user)
1898 end
1916 end
1899
1917
1900 test "#assignable_users should not show the issue author twice" do
1918 test "#assignable_users should not show the issue author twice" do
1901 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1919 assignable_user_ids = Issue.find(1).assignable_users.collect(&:id)
1902 assert_equal 2, assignable_user_ids.length
1920 assert_equal 2, assignable_user_ids.length
1903
1921
1904 assignable_user_ids.each do |user_id|
1922 assignable_user_ids.each do |user_id|
1905 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1923 assert_equal 1, assignable_user_ids.select {|i| i == user_id}.length,
1906 "User #{user_id} appears more or less than once"
1924 "User #{user_id} appears more or less than once"
1907 end
1925 end
1908 end
1926 end
1909
1927
1910 test "#assignable_users with issue_group_assignment should include groups" do
1928 test "#assignable_users with issue_group_assignment should include groups" do
1911 issue = Issue.new(:project => Project.find(2))
1929 issue = Issue.new(:project => Project.find(2))
1912
1930
1913 with_settings :issue_group_assignment => '1' do
1931 with_settings :issue_group_assignment => '1' do
1914 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1932 assert_equal %w(Group User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1915 assert issue.assignable_users.include?(Group.find(11))
1933 assert issue.assignable_users.include?(Group.find(11))
1916 end
1934 end
1917 end
1935 end
1918
1936
1919 test "#assignable_users without issue_group_assignment should not include groups" do
1937 test "#assignable_users without issue_group_assignment should not include groups" do
1920 issue = Issue.new(:project => Project.find(2))
1938 issue = Issue.new(:project => Project.find(2))
1921
1939
1922 with_settings :issue_group_assignment => '0' do
1940 with_settings :issue_group_assignment => '0' do
1923 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1941 assert_equal %w(User), issue.assignable_users.map {|a| a.class.name}.uniq.sort
1924 assert !issue.assignable_users.include?(Group.find(11))
1942 assert !issue.assignable_users.include?(Group.find(11))
1925 end
1943 end
1926 end
1944 end
1927
1945
1928 def test_assignable_users_should_not_include_builtin_groups
1946 def test_assignable_users_should_not_include_builtin_groups
1929 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [1])
1947 Member.create!(:project_id => 1, :principal => Group.non_member, :role_ids => [1])
1930 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [1])
1948 Member.create!(:project_id => 1, :principal => Group.anonymous, :role_ids => [1])
1931 issue = Issue.new(:project => Project.find(1))
1949 issue = Issue.new(:project => Project.find(1))
1932
1950
1933 with_settings :issue_group_assignment => '1' do
1951 with_settings :issue_group_assignment => '1' do
1934 assert_nil issue.assignable_users.detect {|u| u.is_a?(GroupBuiltin)}
1952 assert_nil issue.assignable_users.detect {|u| u.is_a?(GroupBuiltin)}
1935 end
1953 end
1936 end
1954 end
1937
1955
1938 def test_create_should_send_email_notification
1956 def test_create_should_send_email_notification
1939 ActionMailer::Base.deliveries.clear
1957 ActionMailer::Base.deliveries.clear
1940 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1958 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1941 :author_id => 3, :status_id => 1,
1959 :author_id => 3, :status_id => 1,
1942 :priority => IssuePriority.all.first,
1960 :priority => IssuePriority.all.first,
1943 :subject => 'test_create', :estimated_hours => '1:30')
1961 :subject => 'test_create', :estimated_hours => '1:30')
1944 with_settings :notified_events => %w(issue_added) do
1962 with_settings :notified_events => %w(issue_added) do
1945 assert issue.save
1963 assert issue.save
1946 assert_equal 1, ActionMailer::Base.deliveries.size
1964 assert_equal 1, ActionMailer::Base.deliveries.size
1947 end
1965 end
1948 end
1966 end
1949
1967
1950 def test_create_should_send_one_email_notification_with_both_settings
1968 def test_create_should_send_one_email_notification_with_both_settings
1951 ActionMailer::Base.deliveries.clear
1969 ActionMailer::Base.deliveries.clear
1952 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1970 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1953 :author_id => 3, :status_id => 1,
1971 :author_id => 3, :status_id => 1,
1954 :priority => IssuePriority.all.first,
1972 :priority => IssuePriority.all.first,
1955 :subject => 'test_create', :estimated_hours => '1:30')
1973 :subject => 'test_create', :estimated_hours => '1:30')
1956 with_settings :notified_events => %w(issue_added issue_updated) do
1974 with_settings :notified_events => %w(issue_added issue_updated) do
1957 assert issue.save
1975 assert issue.save
1958 assert_equal 1, ActionMailer::Base.deliveries.size
1976 assert_equal 1, ActionMailer::Base.deliveries.size
1959 end
1977 end
1960 end
1978 end
1961
1979
1962 def test_create_should_not_send_email_notification_with_no_setting
1980 def test_create_should_not_send_email_notification_with_no_setting
1963 ActionMailer::Base.deliveries.clear
1981 ActionMailer::Base.deliveries.clear
1964 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1982 issue = Issue.new(:project_id => 1, :tracker_id => 1,
1965 :author_id => 3, :status_id => 1,
1983 :author_id => 3, :status_id => 1,
1966 :priority => IssuePriority.all.first,
1984 :priority => IssuePriority.all.first,
1967 :subject => 'test_create', :estimated_hours => '1:30')
1985 :subject => 'test_create', :estimated_hours => '1:30')
1968 with_settings :notified_events => [] do
1986 with_settings :notified_events => [] do
1969 assert issue.save
1987 assert issue.save
1970 assert_equal 0, ActionMailer::Base.deliveries.size
1988 assert_equal 0, ActionMailer::Base.deliveries.size
1971 end
1989 end
1972 end
1990 end
1973
1991
1974 def test_update_should_notify_previous_assignee
1992 def test_update_should_notify_previous_assignee
1975 ActionMailer::Base.deliveries.clear
1993 ActionMailer::Base.deliveries.clear
1976 user = User.find(3)
1994 user = User.find(3)
1977 user.members.update_all ["mail_notification = ?", false]
1995 user.members.update_all ["mail_notification = ?", false]
1978 user.update_attribute :mail_notification, 'only_assigned'
1996 user.update_attribute :mail_notification, 'only_assigned'
1979
1997
1980 with_settings :notified_events => %w(issue_updated) do
1998 with_settings :notified_events => %w(issue_updated) do
1981 issue = Issue.find(2)
1999 issue = Issue.find(2)
1982 issue.init_journal User.find(1)
2000 issue.init_journal User.find(1)
1983 issue.assigned_to = nil
2001 issue.assigned_to = nil
1984 issue.save!
2002 issue.save!
1985 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
2003 assert_include user.mail, ActionMailer::Base.deliveries.last.bcc
1986 end
2004 end
1987 end
2005 end
1988
2006
1989 def test_stale_issue_should_not_send_email_notification
2007 def test_stale_issue_should_not_send_email_notification
1990 ActionMailer::Base.deliveries.clear
2008 ActionMailer::Base.deliveries.clear
1991 issue = Issue.find(1)
2009 issue = Issue.find(1)
1992 stale = Issue.find(1)
2010 stale = Issue.find(1)
1993
2011
1994 issue.init_journal(User.find(1))
2012 issue.init_journal(User.find(1))
1995 issue.subject = 'Subjet update'
2013 issue.subject = 'Subjet update'
1996 with_settings :notified_events => %w(issue_updated) do
2014 with_settings :notified_events => %w(issue_updated) do
1997 assert issue.save
2015 assert issue.save
1998 assert_equal 1, ActionMailer::Base.deliveries.size
2016 assert_equal 1, ActionMailer::Base.deliveries.size
1999 ActionMailer::Base.deliveries.clear
2017 ActionMailer::Base.deliveries.clear
2000
2018
2001 stale.init_journal(User.find(1))
2019 stale.init_journal(User.find(1))
2002 stale.subject = 'Another subjet update'
2020 stale.subject = 'Another subjet update'
2003 assert_raise ActiveRecord::StaleObjectError do
2021 assert_raise ActiveRecord::StaleObjectError do
2004 stale.save
2022 stale.save
2005 end
2023 end
2006 assert ActionMailer::Base.deliveries.empty?
2024 assert ActionMailer::Base.deliveries.empty?
2007 end
2025 end
2008 end
2026 end
2009
2027
2010 def test_journalized_description
2028 def test_journalized_description
2011 IssueCustomField.delete_all
2029 IssueCustomField.delete_all
2012
2030
2013 i = Issue.first
2031 i = Issue.first
2014 old_description = i.description
2032 old_description = i.description
2015 new_description = "This is the new description"
2033 new_description = "This is the new description"
2016
2034
2017 i.init_journal(User.find(2))
2035 i.init_journal(User.find(2))
2018 i.description = new_description
2036 i.description = new_description
2019 assert_difference 'Journal.count', 1 do
2037 assert_difference 'Journal.count', 1 do
2020 assert_difference 'JournalDetail.count', 1 do
2038 assert_difference 'JournalDetail.count', 1 do
2021 i.save!
2039 i.save!
2022 end
2040 end
2023 end
2041 end
2024
2042
2025 detail = JournalDetail.order('id DESC').first
2043 detail = JournalDetail.order('id DESC').first
2026 assert_equal i, detail.journal.journalized
2044 assert_equal i, detail.journal.journalized
2027 assert_equal 'attr', detail.property
2045 assert_equal 'attr', detail.property
2028 assert_equal 'description', detail.prop_key
2046 assert_equal 'description', detail.prop_key
2029 assert_equal old_description, detail.old_value
2047 assert_equal old_description, detail.old_value
2030 assert_equal new_description, detail.value
2048 assert_equal new_description, detail.value
2031 end
2049 end
2032
2050
2033 def test_blank_descriptions_should_not_be_journalized
2051 def test_blank_descriptions_should_not_be_journalized
2034 IssueCustomField.delete_all
2052 IssueCustomField.delete_all
2035 Issue.where(:id => 1).update_all("description = NULL")
2053 Issue.where(:id => 1).update_all("description = NULL")
2036
2054
2037 i = Issue.find(1)
2055 i = Issue.find(1)
2038 i.init_journal(User.find(2))
2056 i.init_journal(User.find(2))
2039 i.subject = "blank description"
2057 i.subject = "blank description"
2040 i.description = "\r\n"
2058 i.description = "\r\n"
2041
2059
2042 assert_difference 'Journal.count', 1 do
2060 assert_difference 'Journal.count', 1 do
2043 assert_difference 'JournalDetail.count', 1 do
2061 assert_difference 'JournalDetail.count', 1 do
2044 i.save!
2062 i.save!
2045 end
2063 end
2046 end
2064 end
2047 end
2065 end
2048
2066
2049 def test_journalized_multi_custom_field
2067 def test_journalized_multi_custom_field
2050 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
2068 field = IssueCustomField.create!(:name => 'filter', :field_format => 'list',
2051 :is_filter => true, :is_for_all => true,
2069 :is_filter => true, :is_for_all => true,
2052 :tracker_ids => [1],
2070 :tracker_ids => [1],
2053 :possible_values => ['value1', 'value2', 'value3'],
2071 :possible_values => ['value1', 'value2', 'value3'],
2054 :multiple => true)
2072 :multiple => true)
2055
2073
2056 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
2074 issue = Issue.create!(:project_id => 1, :tracker_id => 1,
2057 :subject => 'Test', :author_id => 1)
2075 :subject => 'Test', :author_id => 1)
2058
2076
2059 assert_difference 'Journal.count' do
2077 assert_difference 'Journal.count' do
2060 assert_difference 'JournalDetail.count' do
2078 assert_difference 'JournalDetail.count' do
2061 issue.init_journal(User.first)
2079 issue.init_journal(User.first)
2062 issue.custom_field_values = {field.id => ['value1']}
2080 issue.custom_field_values = {field.id => ['value1']}
2063 issue.save!
2081 issue.save!
2064 end
2082 end
2065 assert_difference 'JournalDetail.count' do
2083 assert_difference 'JournalDetail.count' do
2066 issue.init_journal(User.first)
2084 issue.init_journal(User.first)
2067 issue.custom_field_values = {field.id => ['value1', 'value2']}
2085 issue.custom_field_values = {field.id => ['value1', 'value2']}
2068 issue.save!
2086 issue.save!
2069 end
2087 end
2070 assert_difference 'JournalDetail.count', 2 do
2088 assert_difference 'JournalDetail.count', 2 do
2071 issue.init_journal(User.first)
2089 issue.init_journal(User.first)
2072 issue.custom_field_values = {field.id => ['value3', 'value2']}
2090 issue.custom_field_values = {field.id => ['value3', 'value2']}
2073 issue.save!
2091 issue.save!
2074 end
2092 end
2075 assert_difference 'JournalDetail.count', 2 do
2093 assert_difference 'JournalDetail.count', 2 do
2076 issue.init_journal(User.first)
2094 issue.init_journal(User.first)
2077 issue.custom_field_values = {field.id => nil}
2095 issue.custom_field_values = {field.id => nil}
2078 issue.save!
2096 issue.save!
2079 end
2097 end
2080 end
2098 end
2081 end
2099 end
2082
2100
2083 def test_description_eol_should_be_normalized
2101 def test_description_eol_should_be_normalized
2084 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
2102 i = Issue.new(:description => "CR \r LF \n CRLF \r\n")
2085 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
2103 assert_equal "CR \r\n LF \r\n CRLF \r\n", i.description
2086 end
2104 end
2087
2105
2088 def test_saving_twice_should_not_duplicate_journal_details
2106 def test_saving_twice_should_not_duplicate_journal_details
2089 i = Issue.first
2107 i = Issue.first
2090 i.init_journal(User.find(2), 'Some notes')
2108 i.init_journal(User.find(2), 'Some notes')
2091 # initial changes
2109 # initial changes
2092 i.subject = 'New subject'
2110 i.subject = 'New subject'
2093 i.done_ratio = i.done_ratio + 10
2111 i.done_ratio = i.done_ratio + 10
2094 assert_difference 'Journal.count' do
2112 assert_difference 'Journal.count' do
2095 assert i.save
2113 assert i.save
2096 end
2114 end
2097 # 1 more change
2115 # 1 more change
2098 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
2116 i.priority = IssuePriority.where("id <> ?", i.priority_id).first
2099 assert_no_difference 'Journal.count' do
2117 assert_no_difference 'Journal.count' do
2100 assert_difference 'JournalDetail.count', 1 do
2118 assert_difference 'JournalDetail.count', 1 do
2101 i.save
2119 i.save
2102 end
2120 end
2103 end
2121 end
2104 # no more change
2122 # no more change
2105 assert_no_difference 'Journal.count' do
2123 assert_no_difference 'Journal.count' do
2106 assert_no_difference 'JournalDetail.count' do
2124 assert_no_difference 'JournalDetail.count' do
2107 i.save
2125 i.save
2108 end
2126 end
2109 end
2127 end
2110 end
2128 end
2111
2129
2112 def test_all_dependent_issues
2130 def test_all_dependent_issues
2113 IssueRelation.delete_all
2131 IssueRelation.delete_all
2114 assert IssueRelation.create!(:issue_from => Issue.find(1),
2132 assert IssueRelation.create!(:issue_from => Issue.find(1),
2115 :issue_to => Issue.find(2),
2133 :issue_to => Issue.find(2),
2116 :relation_type => IssueRelation::TYPE_PRECEDES)
2134 :relation_type => IssueRelation::TYPE_PRECEDES)
2117 assert IssueRelation.create!(:issue_from => Issue.find(2),
2135 assert IssueRelation.create!(:issue_from => Issue.find(2),
2118 :issue_to => Issue.find(3),
2136 :issue_to => Issue.find(3),
2119 :relation_type => IssueRelation::TYPE_PRECEDES)
2137 :relation_type => IssueRelation::TYPE_PRECEDES)
2120 assert IssueRelation.create!(:issue_from => Issue.find(3),
2138 assert IssueRelation.create!(:issue_from => Issue.find(3),
2121 :issue_to => Issue.find(8),
2139 :issue_to => Issue.find(8),
2122 :relation_type => IssueRelation::TYPE_PRECEDES)
2140 :relation_type => IssueRelation::TYPE_PRECEDES)
2123
2141
2124 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2142 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2125 end
2143 end
2126
2144
2127 def test_all_dependent_issues_with_subtask
2145 def test_all_dependent_issues_with_subtask
2128 IssueRelation.delete_all
2146 IssueRelation.delete_all
2129
2147
2130 project = Project.generate!(:name => "testproject")
2148 project = Project.generate!(:name => "testproject")
2131
2149
2132 parentIssue = Issue.generate!(:project => project)
2150 parentIssue = Issue.generate!(:project => project)
2133 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2151 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2134 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2152 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2135
2153
2136 assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
2154 assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
2137 end
2155 end
2138
2156
2139 def test_all_dependent_issues_does_not_include_self
2157 def test_all_dependent_issues_does_not_include_self
2140 IssueRelation.delete_all
2158 IssueRelation.delete_all
2141
2159
2142 project = Project.generate!(:name => "testproject")
2160 project = Project.generate!(:name => "testproject")
2143
2161
2144 parentIssue = Issue.generate!(:project => project)
2162 parentIssue = Issue.generate!(:project => project)
2145 childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2163 childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2146
2164
2147 assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
2165 assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
2148 end
2166 end
2149
2167
2150 def test_all_dependent_issues_with_parenttask_and_sibling
2168 def test_all_dependent_issues_with_parenttask_and_sibling
2151 IssueRelation.delete_all
2169 IssueRelation.delete_all
2152
2170
2153 project = Project.generate!(:name => "testproject")
2171 project = Project.generate!(:name => "testproject")
2154
2172
2155 parentIssue = Issue.generate!(:project => project)
2173 parentIssue = Issue.generate!(:project => project)
2156 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2174 childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2157 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2175 childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
2158
2176
2159 assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
2177 assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
2160 end
2178 end
2161
2179
2162 def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
2180 def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
2163 IssueRelation.delete_all
2181 IssueRelation.delete_all
2164
2182
2165 project = Project.generate!(:name => "testproject")
2183 project = Project.generate!(:name => "testproject")
2166
2184
2167 parentIssue1 = Issue.generate!(:project => project)
2185 parentIssue1 = Issue.generate!(:project => project)
2168 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2186 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2169 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2187 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2170
2188
2171 parentIssue2 = Issue.generate!(:project => project)
2189 parentIssue2 = Issue.generate!(:project => project)
2172 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2190 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2173 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2191 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2174
2192
2175
2193
2176 assert IssueRelation.create(:issue_from => parentIssue1,
2194 assert IssueRelation.create(:issue_from => parentIssue1,
2177 :issue_to => childIssue2_2,
2195 :issue_to => childIssue2_2,
2178 :relation_type => IssueRelation::TYPE_BLOCKS)
2196 :relation_type => IssueRelation::TYPE_BLOCKS)
2179
2197
2180 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
2198 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
2181 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2199 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2182 end
2200 end
2183
2201
2184 def test_all_dependent_issues_with_relation_to_parent_in_other_tree
2202 def test_all_dependent_issues_with_relation_to_parent_in_other_tree
2185 IssueRelation.delete_all
2203 IssueRelation.delete_all
2186
2204
2187 project = Project.generate!(:name => "testproject")
2205 project = Project.generate!(:name => "testproject")
2188
2206
2189 parentIssue1 = Issue.generate!(:project => project)
2207 parentIssue1 = Issue.generate!(:project => project)
2190 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2208 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2191 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2209 childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2192
2210
2193 parentIssue2 = Issue.generate!(:project => project)
2211 parentIssue2 = Issue.generate!(:project => project)
2194 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2212 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2195 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2213 childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2196
2214
2197
2215
2198 assert IssueRelation.create(:issue_from => parentIssue1,
2216 assert IssueRelation.create(:issue_from => parentIssue1,
2199 :issue_to => parentIssue2,
2217 :issue_to => parentIssue2,
2200 :relation_type => IssueRelation::TYPE_BLOCKS)
2218 :relation_type => IssueRelation::TYPE_BLOCKS)
2201
2219
2202 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
2220 assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
2203 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2221 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2204 end
2222 end
2205
2223
2206 def test_all_dependent_issues_with_transitive_relation
2224 def test_all_dependent_issues_with_transitive_relation
2207 IssueRelation.delete_all
2225 IssueRelation.delete_all
2208
2226
2209 project = Project.generate!(:name => "testproject")
2227 project = Project.generate!(:name => "testproject")
2210
2228
2211 parentIssue1 = Issue.generate!(:project => project)
2229 parentIssue1 = Issue.generate!(:project => project)
2212 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2230 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2213
2231
2214 parentIssue2 = Issue.generate!(:project => project)
2232 parentIssue2 = Issue.generate!(:project => project)
2215 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2233 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2216
2234
2217 independentIssue = Issue.generate!(:project => project)
2235 independentIssue = Issue.generate!(:project => project)
2218
2236
2219 assert IssueRelation.create(:issue_from => parentIssue1,
2237 assert IssueRelation.create(:issue_from => parentIssue1,
2220 :issue_to => childIssue2_1,
2238 :issue_to => childIssue2_1,
2221 :relation_type => IssueRelation::TYPE_RELATES)
2239 :relation_type => IssueRelation::TYPE_RELATES)
2222
2240
2223 assert IssueRelation.create(:issue_from => childIssue2_1,
2241 assert IssueRelation.create(:issue_from => childIssue2_1,
2224 :issue_to => independentIssue,
2242 :issue_to => independentIssue,
2225 :relation_type => IssueRelation::TYPE_RELATES)
2243 :relation_type => IssueRelation::TYPE_RELATES)
2226
2244
2227 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2245 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2228 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2246 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2229 end
2247 end
2230
2248
2231 def test_all_dependent_issues_with_transitive_relation2
2249 def test_all_dependent_issues_with_transitive_relation2
2232 IssueRelation.delete_all
2250 IssueRelation.delete_all
2233
2251
2234 project = Project.generate!(:name => "testproject")
2252 project = Project.generate!(:name => "testproject")
2235
2253
2236 parentIssue1 = Issue.generate!(:project => project)
2254 parentIssue1 = Issue.generate!(:project => project)
2237 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2255 childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
2238
2256
2239 parentIssue2 = Issue.generate!(:project => project)
2257 parentIssue2 = Issue.generate!(:project => project)
2240 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2258 childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
2241
2259
2242 independentIssue = Issue.generate!(:project => project)
2260 independentIssue = Issue.generate!(:project => project)
2243
2261
2244 assert IssueRelation.create(:issue_from => parentIssue1,
2262 assert IssueRelation.create(:issue_from => parentIssue1,
2245 :issue_to => independentIssue,
2263 :issue_to => independentIssue,
2246 :relation_type => IssueRelation::TYPE_RELATES)
2264 :relation_type => IssueRelation::TYPE_RELATES)
2247
2265
2248 assert IssueRelation.create(:issue_from => independentIssue,
2266 assert IssueRelation.create(:issue_from => independentIssue,
2249 :issue_to => childIssue2_1,
2267 :issue_to => childIssue2_1,
2250 :relation_type => IssueRelation::TYPE_RELATES)
2268 :relation_type => IssueRelation::TYPE_RELATES)
2251
2269
2252 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2270 assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
2253 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2271 parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
2254
2272
2255 end
2273 end
2256
2274
2257 def test_all_dependent_issues_with_persistent_circular_dependency
2275 def test_all_dependent_issues_with_persistent_circular_dependency
2258 IssueRelation.delete_all
2276 IssueRelation.delete_all
2259 assert IssueRelation.create!(:issue_from => Issue.find(1),
2277 assert IssueRelation.create!(:issue_from => Issue.find(1),
2260 :issue_to => Issue.find(2),
2278 :issue_to => Issue.find(2),
2261 :relation_type => IssueRelation::TYPE_PRECEDES)
2279 :relation_type => IssueRelation::TYPE_PRECEDES)
2262 assert IssueRelation.create!(:issue_from => Issue.find(2),
2280 assert IssueRelation.create!(:issue_from => Issue.find(2),
2263 :issue_to => Issue.find(3),
2281 :issue_to => Issue.find(3),
2264 :relation_type => IssueRelation::TYPE_PRECEDES)
2282 :relation_type => IssueRelation::TYPE_PRECEDES)
2265
2283
2266 r = IssueRelation.create!(:issue_from => Issue.find(3),
2284 r = IssueRelation.create!(:issue_from => Issue.find(3),
2267 :issue_to => Issue.find(7),
2285 :issue_to => Issue.find(7),
2268 :relation_type => IssueRelation::TYPE_PRECEDES)
2286 :relation_type => IssueRelation::TYPE_PRECEDES)
2269 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2287 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2270
2288
2271 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
2289 assert_equal [2, 3], Issue.find(1).all_dependent_issues.collect(&:id).sort
2272 end
2290 end
2273
2291
2274 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
2292 def test_all_dependent_issues_with_persistent_multiple_circular_dependencies
2275 IssueRelation.delete_all
2293 IssueRelation.delete_all
2276 assert IssueRelation.create!(:issue_from => Issue.find(1),
2294 assert IssueRelation.create!(:issue_from => Issue.find(1),
2277 :issue_to => Issue.find(2),
2295 :issue_to => Issue.find(2),
2278 :relation_type => IssueRelation::TYPE_RELATES)
2296 :relation_type => IssueRelation::TYPE_RELATES)
2279 assert IssueRelation.create!(:issue_from => Issue.find(2),
2297 assert IssueRelation.create!(:issue_from => Issue.find(2),
2280 :issue_to => Issue.find(3),
2298 :issue_to => Issue.find(3),
2281 :relation_type => IssueRelation::TYPE_RELATES)
2299 :relation_type => IssueRelation::TYPE_RELATES)
2282 assert IssueRelation.create!(:issue_from => Issue.find(3),
2300 assert IssueRelation.create!(:issue_from => Issue.find(3),
2283 :issue_to => Issue.find(8),
2301 :issue_to => Issue.find(8),
2284 :relation_type => IssueRelation::TYPE_RELATES)
2302 :relation_type => IssueRelation::TYPE_RELATES)
2285
2303
2286 r = IssueRelation.create!(:issue_from => Issue.find(8),
2304 r = IssueRelation.create!(:issue_from => Issue.find(8),
2287 :issue_to => Issue.find(7),
2305 :issue_to => Issue.find(7),
2288 :relation_type => IssueRelation::TYPE_RELATES)
2306 :relation_type => IssueRelation::TYPE_RELATES)
2289 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 2")
2307 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 2")
2290
2308
2291 r = IssueRelation.create!(:issue_from => Issue.find(3),
2309 r = IssueRelation.create!(:issue_from => Issue.find(3),
2292 :issue_to => Issue.find(7),
2310 :issue_to => Issue.find(7),
2293 :relation_type => IssueRelation::TYPE_RELATES)
2311 :relation_type => IssueRelation::TYPE_RELATES)
2294 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2312 IssueRelation.where(["id = ?", r.id]).update_all("issue_to_id = 1")
2295
2313
2296 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2314 assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
2297 end
2315 end
2298
2316
2299 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2317 test "#done_ratio should use the issue_status according to Setting.issue_done_ratio" do
2300 @issue = Issue.find(1)
2318 @issue = Issue.find(1)
2301 @issue_status = IssueStatus.find(1)
2319 @issue_status = IssueStatus.find(1)
2302 @issue_status.update_attribute(:default_done_ratio, 50)
2320 @issue_status.update_attribute(:default_done_ratio, 50)
2303 @issue2 = Issue.find(2)
2321 @issue2 = Issue.find(2)
2304 @issue_status2 = IssueStatus.find(2)
2322 @issue_status2 = IssueStatus.find(2)
2305 @issue_status2.update_attribute(:default_done_ratio, 0)
2323 @issue_status2.update_attribute(:default_done_ratio, 0)
2306
2324
2307 with_settings :issue_done_ratio => 'issue_field' do
2325 with_settings :issue_done_ratio => 'issue_field' do
2308 assert_equal 0, @issue.done_ratio
2326 assert_equal 0, @issue.done_ratio
2309 assert_equal 30, @issue2.done_ratio
2327 assert_equal 30, @issue2.done_ratio
2310 end
2328 end
2311
2329
2312 with_settings :issue_done_ratio => 'issue_status' do
2330 with_settings :issue_done_ratio => 'issue_status' do
2313 assert_equal 50, @issue.done_ratio
2331 assert_equal 50, @issue.done_ratio
2314 assert_equal 0, @issue2.done_ratio
2332 assert_equal 0, @issue2.done_ratio
2315 end
2333 end
2316 end
2334 end
2317
2335
2318 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2336 test "#update_done_ratio_from_issue_status should update done_ratio according to Setting.issue_done_ratio" do
2319 @issue = Issue.find(1)
2337 @issue = Issue.find(1)
2320 @issue_status = IssueStatus.find(1)
2338 @issue_status = IssueStatus.find(1)
2321 @issue_status.update_attribute(:default_done_ratio, 50)
2339 @issue_status.update_attribute(:default_done_ratio, 50)
2322 @issue2 = Issue.find(2)
2340 @issue2 = Issue.find(2)
2323 @issue_status2 = IssueStatus.find(2)
2341 @issue_status2 = IssueStatus.find(2)
2324 @issue_status2.update_attribute(:default_done_ratio, 0)
2342 @issue_status2.update_attribute(:default_done_ratio, 0)
2325
2343
2326 with_settings :issue_done_ratio => 'issue_field' do
2344 with_settings :issue_done_ratio => 'issue_field' do
2327 @issue.update_done_ratio_from_issue_status
2345 @issue.update_done_ratio_from_issue_status
2328 @issue2.update_done_ratio_from_issue_status
2346 @issue2.update_done_ratio_from_issue_status
2329
2347
2330 assert_equal 0, @issue.read_attribute(:done_ratio)
2348 assert_equal 0, @issue.read_attribute(:done_ratio)
2331 assert_equal 30, @issue2.read_attribute(:done_ratio)
2349 assert_equal 30, @issue2.read_attribute(:done_ratio)
2332 end
2350 end
2333
2351
2334 with_settings :issue_done_ratio => 'issue_status' do
2352 with_settings :issue_done_ratio => 'issue_status' do
2335 @issue.update_done_ratio_from_issue_status
2353 @issue.update_done_ratio_from_issue_status
2336 @issue2.update_done_ratio_from_issue_status
2354 @issue2.update_done_ratio_from_issue_status
2337
2355
2338 assert_equal 50, @issue.read_attribute(:done_ratio)
2356 assert_equal 50, @issue.read_attribute(:done_ratio)
2339 assert_equal 0, @issue2.read_attribute(:done_ratio)
2357 assert_equal 0, @issue2.read_attribute(:done_ratio)
2340 end
2358 end
2341 end
2359 end
2342
2360
2343 test "#by_tracker" do
2361 test "#by_tracker" do
2344 User.current = User.anonymous
2362 User.current = User.anonymous
2345 groups = Issue.by_tracker(Project.find(1))
2363 groups = Issue.by_tracker(Project.find(1))
2346 assert_equal 3, groups.count
2364 assert_equal 3, groups.count
2347 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2365 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2348 end
2366 end
2349
2367
2350 test "#by_version" do
2368 test "#by_version" do
2351 User.current = User.anonymous
2369 User.current = User.anonymous
2352 groups = Issue.by_version(Project.find(1))
2370 groups = Issue.by_version(Project.find(1))
2353 assert_equal 3, groups.count
2371 assert_equal 3, groups.count
2354 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2372 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2355 end
2373 end
2356
2374
2357 test "#by_priority" do
2375 test "#by_priority" do
2358 User.current = User.anonymous
2376 User.current = User.anonymous
2359 groups = Issue.by_priority(Project.find(1))
2377 groups = Issue.by_priority(Project.find(1))
2360 assert_equal 4, groups.count
2378 assert_equal 4, groups.count
2361 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2379 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2362 end
2380 end
2363
2381
2364 test "#by_category" do
2382 test "#by_category" do
2365 User.current = User.anonymous
2383 User.current = User.anonymous
2366 groups = Issue.by_category(Project.find(1))
2384 groups = Issue.by_category(Project.find(1))
2367 assert_equal 2, groups.count
2385 assert_equal 2, groups.count
2368 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2386 assert_equal 3, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2369 end
2387 end
2370
2388
2371 test "#by_assigned_to" do
2389 test "#by_assigned_to" do
2372 User.current = User.anonymous
2390 User.current = User.anonymous
2373 groups = Issue.by_assigned_to(Project.find(1))
2391 groups = Issue.by_assigned_to(Project.find(1))
2374 assert_equal 2, groups.count
2392 assert_equal 2, groups.count
2375 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2393 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2376 end
2394 end
2377
2395
2378 test "#by_author" do
2396 test "#by_author" do
2379 User.current = User.anonymous
2397 User.current = User.anonymous
2380 groups = Issue.by_author(Project.find(1))
2398 groups = Issue.by_author(Project.find(1))
2381 assert_equal 4, groups.count
2399 assert_equal 4, groups.count
2382 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2400 assert_equal 7, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2383 end
2401 end
2384
2402
2385 test "#by_subproject" do
2403 test "#by_subproject" do
2386 User.current = User.anonymous
2404 User.current = User.anonymous
2387 groups = Issue.by_subproject(Project.find(1))
2405 groups = Issue.by_subproject(Project.find(1))
2388 # Private descendant not visible
2406 # Private descendant not visible
2389 assert_equal 1, groups.count
2407 assert_equal 1, groups.count
2390 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2408 assert_equal 2, groups.inject(0) {|sum, group| sum + group['total'].to_i}
2391 end
2409 end
2392
2410
2393 def test_recently_updated_scope
2411 def test_recently_updated_scope
2394 #should return the last updated issue
2412 #should return the last updated issue
2395 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2413 assert_equal Issue.reorder("updated_on DESC").first, Issue.recently_updated.limit(1).first
2396 end
2414 end
2397
2415
2398 def test_on_active_projects_scope
2416 def test_on_active_projects_scope
2399 assert Project.find(2).archive
2417 assert Project.find(2).archive
2400
2418
2401 before = Issue.on_active_project.length
2419 before = Issue.on_active_project.length
2402 # test inclusion to results
2420 # test inclusion to results
2403 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2421 issue = Issue.generate!(:tracker => Project.find(2).trackers.first)
2404 assert_equal before + 1, Issue.on_active_project.length
2422 assert_equal before + 1, Issue.on_active_project.length
2405
2423
2406 # Move to an archived project
2424 # Move to an archived project
2407 issue.project = Project.find(2)
2425 issue.project = Project.find(2)
2408 assert issue.save
2426 assert issue.save
2409 assert_equal before, Issue.on_active_project.length
2427 assert_equal before, Issue.on_active_project.length
2410 end
2428 end
2411
2429
2412 test "Issue#recipients should include project recipients" do
2430 test "Issue#recipients should include project recipients" do
2413 issue = Issue.generate!
2431 issue = Issue.generate!
2414 assert issue.project.recipients.present?
2432 assert issue.project.recipients.present?
2415 issue.project.recipients.each do |project_recipient|
2433 issue.project.recipients.each do |project_recipient|
2416 assert issue.recipients.include?(project_recipient)
2434 assert issue.recipients.include?(project_recipient)
2417 end
2435 end
2418 end
2436 end
2419
2437
2420 test "Issue#recipients should include the author if the author is active" do
2438 test "Issue#recipients should include the author if the author is active" do
2421 issue = Issue.generate!(:author => User.generate!)
2439 issue = Issue.generate!(:author => User.generate!)
2422 assert issue.author, "No author set for Issue"
2440 assert issue.author, "No author set for Issue"
2423 assert issue.recipients.include?(issue.author.mail)
2441 assert issue.recipients.include?(issue.author.mail)
2424 end
2442 end
2425
2443
2426 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2444 test "Issue#recipients should include the assigned to user if the assigned to user is active" do
2427 issue = Issue.generate!(:assigned_to => User.generate!)
2445 issue = Issue.generate!(:assigned_to => User.generate!)
2428 assert issue.assigned_to, "No assigned_to set for Issue"
2446 assert issue.assigned_to, "No assigned_to set for Issue"
2429 assert issue.recipients.include?(issue.assigned_to.mail)
2447 assert issue.recipients.include?(issue.assigned_to.mail)
2430 end
2448 end
2431
2449
2432 test "Issue#recipients should not include users who opt out of all email" do
2450 test "Issue#recipients should not include users who opt out of all email" do
2433 issue = Issue.generate!(:author => User.generate!)
2451 issue = Issue.generate!(:author => User.generate!)
2434 issue.author.update_attribute(:mail_notification, :none)
2452 issue.author.update_attribute(:mail_notification, :none)
2435 assert !issue.recipients.include?(issue.author.mail)
2453 assert !issue.recipients.include?(issue.author.mail)
2436 end
2454 end
2437
2455
2438 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2456 test "Issue#recipients should not include the issue author if they are only notified of assigned issues" do
2439 issue = Issue.generate!(:author => User.generate!)
2457 issue = Issue.generate!(:author => User.generate!)
2440 issue.author.update_attribute(:mail_notification, :only_assigned)
2458 issue.author.update_attribute(:mail_notification, :only_assigned)
2441 assert !issue.recipients.include?(issue.author.mail)
2459 assert !issue.recipients.include?(issue.author.mail)
2442 end
2460 end
2443
2461
2444 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2462 test "Issue#recipients should not include the assigned user if they are only notified of owned issues" do
2445 issue = Issue.generate!(:assigned_to => User.generate!)
2463 issue = Issue.generate!(:assigned_to => User.generate!)
2446 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
2464 issue.assigned_to.update_attribute(:mail_notification, :only_owner)
2447 assert !issue.recipients.include?(issue.assigned_to.mail)
2465 assert !issue.recipients.include?(issue.assigned_to.mail)
2448 end
2466 end
2449
2467
2450 def test_last_journal_id_with_journals_should_return_the_journal_id
2468 def test_last_journal_id_with_journals_should_return_the_journal_id
2451 assert_equal 2, Issue.find(1).last_journal_id
2469 assert_equal 2, Issue.find(1).last_journal_id
2452 end
2470 end
2453
2471
2454 def test_last_journal_id_without_journals_should_return_nil
2472 def test_last_journal_id_without_journals_should_return_nil
2455 assert_nil Issue.find(3).last_journal_id
2473 assert_nil Issue.find(3).last_journal_id
2456 end
2474 end
2457
2475
2458 def test_journals_after_should_return_journals_with_greater_id
2476 def test_journals_after_should_return_journals_with_greater_id
2459 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2477 assert_equal [Journal.find(2)], Issue.find(1).journals_after('1')
2460 assert_equal [], Issue.find(1).journals_after('2')
2478 assert_equal [], Issue.find(1).journals_after('2')
2461 end
2479 end
2462
2480
2463 def test_journals_after_with_blank_arg_should_return_all_journals
2481 def test_journals_after_with_blank_arg_should_return_all_journals
2464 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2482 assert_equal [Journal.find(1), Journal.find(2)], Issue.find(1).journals_after('')
2465 end
2483 end
2466
2484
2467 def test_css_classes_should_include_tracker
2485 def test_css_classes_should_include_tracker
2468 issue = Issue.new(:tracker => Tracker.find(2))
2486 issue = Issue.new(:tracker => Tracker.find(2))
2469 classes = issue.css_classes.split(' ')
2487 classes = issue.css_classes.split(' ')
2470 assert_include 'tracker-2', classes
2488 assert_include 'tracker-2', classes
2471 end
2489 end
2472
2490
2473 def test_css_classes_should_include_priority
2491 def test_css_classes_should_include_priority
2474 issue = Issue.new(:priority => IssuePriority.find(8))
2492 issue = Issue.new(:priority => IssuePriority.find(8))
2475 classes = issue.css_classes.split(' ')
2493 classes = issue.css_classes.split(' ')
2476 assert_include 'priority-8', classes
2494 assert_include 'priority-8', classes
2477 assert_include 'priority-highest', classes
2495 assert_include 'priority-highest', classes
2478 end
2496 end
2479
2497
2480 def test_css_classes_should_include_user_and_group_assignment
2498 def test_css_classes_should_include_user_and_group_assignment
2481 project = Project.first
2499 project = Project.first
2482 user = User.generate!
2500 user = User.generate!
2483 group = Group.generate!
2501 group = Group.generate!
2484 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
2502 Member.create!(:principal => group, :project => project, :role_ids => [1, 2])
2485 group.users << user
2503 group.users << user
2486 assert user.member_of?(project)
2504 assert user.member_of?(project)
2487 issue1 = Issue.generate(:assigned_to_id => group.id)
2505 issue1 = Issue.generate(:assigned_to_id => group.id)
2488 assert_include 'assigned-to-my-group', issue1.css_classes(user)
2506 assert_include 'assigned-to-my-group', issue1.css_classes(user)
2489 assert_not_include 'assigned-to-me', issue1.css_classes(user)
2507 assert_not_include 'assigned-to-me', issue1.css_classes(user)
2490 issue2 = Issue.generate(:assigned_to_id => user.id)
2508 issue2 = Issue.generate(:assigned_to_id => user.id)
2491 assert_not_include 'assigned-to-my-group', issue2.css_classes(user)
2509 assert_not_include 'assigned-to-my-group', issue2.css_classes(user)
2492 assert_include 'assigned-to-me', issue2.css_classes(user)
2510 assert_include 'assigned-to-me', issue2.css_classes(user)
2493 end
2511 end
2494
2512
2495 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2513 def test_save_attachments_with_hash_should_save_attachments_in_keys_order
2496 set_tmp_attachments_directory
2514 set_tmp_attachments_directory
2497 issue = Issue.generate!
2515 issue = Issue.generate!
2498 issue.save_attachments({
2516 issue.save_attachments({
2499 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2517 'p0' => {'file' => mock_file_with_options(:original_filename => 'upload')},
2500 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2518 '3' => {'file' => mock_file_with_options(:original_filename => 'bar')},
2501 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2519 '1' => {'file' => mock_file_with_options(:original_filename => 'foo')}
2502 })
2520 })
2503 issue.attach_saved_attachments
2521 issue.attach_saved_attachments
2504
2522
2505 assert_equal 3, issue.reload.attachments.count
2523 assert_equal 3, issue.reload.attachments.count
2506 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2524 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
2507 end
2525 end
2508
2526
2509 def test_closed_on_should_be_nil_when_creating_an_open_issue
2527 def test_closed_on_should_be_nil_when_creating_an_open_issue
2510 issue = Issue.generate!(:status_id => 1).reload
2528 issue = Issue.generate!(:status_id => 1).reload
2511 assert !issue.closed?
2529 assert !issue.closed?
2512 assert_nil issue.closed_on
2530 assert_nil issue.closed_on
2513 end
2531 end
2514
2532
2515 def test_closed_on_should_be_set_when_creating_a_closed_issue
2533 def test_closed_on_should_be_set_when_creating_a_closed_issue
2516 issue = Issue.generate!(:status_id => 5).reload
2534 issue = Issue.generate!(:status_id => 5).reload
2517 assert issue.closed?
2535 assert issue.closed?
2518 assert_not_nil issue.closed_on
2536 assert_not_nil issue.closed_on
2519 assert_equal issue.updated_on, issue.closed_on
2537 assert_equal issue.updated_on, issue.closed_on
2520 assert_equal issue.created_on, issue.closed_on
2538 assert_equal issue.created_on, issue.closed_on
2521 end
2539 end
2522
2540
2523 def test_closed_on_should_be_nil_when_updating_an_open_issue
2541 def test_closed_on_should_be_nil_when_updating_an_open_issue
2524 issue = Issue.find(1)
2542 issue = Issue.find(1)
2525 issue.subject = 'Not closed yet'
2543 issue.subject = 'Not closed yet'
2526 issue.save!
2544 issue.save!
2527 issue.reload
2545 issue.reload
2528 assert_nil issue.closed_on
2546 assert_nil issue.closed_on
2529 end
2547 end
2530
2548
2531 def test_closed_on_should_be_set_when_closing_an_open_issue
2549 def test_closed_on_should_be_set_when_closing_an_open_issue
2532 issue = Issue.find(1)
2550 issue = Issue.find(1)
2533 issue.subject = 'Now closed'
2551 issue.subject = 'Now closed'
2534 issue.status_id = 5
2552 issue.status_id = 5
2535 issue.save!
2553 issue.save!
2536 issue.reload
2554 issue.reload
2537 assert_not_nil issue.closed_on
2555 assert_not_nil issue.closed_on
2538 assert_equal issue.updated_on, issue.closed_on
2556 assert_equal issue.updated_on, issue.closed_on
2539 end
2557 end
2540
2558
2541 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2559 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
2542 issue = Issue.open(false).first
2560 issue = Issue.open(false).first
2543 was_closed_on = issue.closed_on
2561 was_closed_on = issue.closed_on
2544 assert_not_nil was_closed_on
2562 assert_not_nil was_closed_on
2545 issue.subject = 'Updating a closed issue'
2563 issue.subject = 'Updating a closed issue'
2546 issue.save!
2564 issue.save!
2547 issue.reload
2565 issue.reload
2548 assert_equal was_closed_on, issue.closed_on
2566 assert_equal was_closed_on, issue.closed_on
2549 end
2567 end
2550
2568
2551 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2569 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
2552 issue = Issue.open(false).first
2570 issue = Issue.open(false).first
2553 was_closed_on = issue.closed_on
2571 was_closed_on = issue.closed_on
2554 assert_not_nil was_closed_on
2572 assert_not_nil was_closed_on
2555 issue.subject = 'Reopening a closed issue'
2573 issue.subject = 'Reopening a closed issue'
2556 issue.status_id = 1
2574 issue.status_id = 1
2557 issue.save!
2575 issue.save!
2558 issue.reload
2576 issue.reload
2559 assert !issue.closed?
2577 assert !issue.closed?
2560 assert_equal was_closed_on, issue.closed_on
2578 assert_equal was_closed_on, issue.closed_on
2561 end
2579 end
2562
2580
2563 def test_status_was_should_return_nil_for_new_issue
2581 def test_status_was_should_return_nil_for_new_issue
2564 issue = Issue.new
2582 issue = Issue.new
2565 assert_nil issue.status_was
2583 assert_nil issue.status_was
2566 end
2584 end
2567
2585
2568 def test_status_was_should_return_status_before_change
2586 def test_status_was_should_return_status_before_change
2569 issue = Issue.find(1)
2587 issue = Issue.find(1)
2570 issue.status = IssueStatus.find(2)
2588 issue.status = IssueStatus.find(2)
2571 assert_equal IssueStatus.find(1), issue.status_was
2589 assert_equal IssueStatus.find(1), issue.status_was
2572 end
2590 end
2573
2591
2574 def test_status_was_should_return_status_before_change_with_status_id
2592 def test_status_was_should_return_status_before_change_with_status_id
2575 issue = Issue.find(1)
2593 issue = Issue.find(1)
2576 assert_equal IssueStatus.find(1), issue.status
2594 assert_equal IssueStatus.find(1), issue.status
2577 issue.status_id = 2
2595 issue.status_id = 2
2578 assert_equal IssueStatus.find(1), issue.status_was
2596 assert_equal IssueStatus.find(1), issue.status_was
2579 end
2597 end
2580
2598
2581 def test_status_was_should_be_reset_on_save
2599 def test_status_was_should_be_reset_on_save
2582 issue = Issue.find(1)
2600 issue = Issue.find(1)
2583 issue.status = IssueStatus.find(2)
2601 issue.status = IssueStatus.find(2)
2584 assert_equal IssueStatus.find(1), issue.status_was
2602 assert_equal IssueStatus.find(1), issue.status_was
2585 assert issue.save!
2603 assert issue.save!
2586 assert_equal IssueStatus.find(2), issue.status_was
2604 assert_equal IssueStatus.find(2), issue.status_was
2587 end
2605 end
2588
2606
2589 def test_closing_should_return_true_when_closing_an_issue
2607 def test_closing_should_return_true_when_closing_an_issue
2590 issue = Issue.find(1)
2608 issue = Issue.find(1)
2591 issue.status = IssueStatus.find(2)
2609 issue.status = IssueStatus.find(2)
2592 assert_equal false, issue.closing?
2610 assert_equal false, issue.closing?
2593 issue.status = IssueStatus.find(5)
2611 issue.status = IssueStatus.find(5)
2594 assert_equal true, issue.closing?
2612 assert_equal true, issue.closing?
2595 end
2613 end
2596
2614
2597 def test_closing_should_return_true_when_closing_an_issue_with_status_id
2615 def test_closing_should_return_true_when_closing_an_issue_with_status_id
2598 issue = Issue.find(1)
2616 issue = Issue.find(1)
2599 issue.status_id = 2
2617 issue.status_id = 2
2600 assert_equal false, issue.closing?
2618 assert_equal false, issue.closing?
2601 issue.status_id = 5
2619 issue.status_id = 5
2602 assert_equal true, issue.closing?
2620 assert_equal true, issue.closing?
2603 end
2621 end
2604
2622
2605 def test_closing_should_return_true_for_new_closed_issue
2623 def test_closing_should_return_true_for_new_closed_issue
2606 issue = Issue.new
2624 issue = Issue.new
2607 assert_equal false, issue.closing?
2625 assert_equal false, issue.closing?
2608 issue.status = IssueStatus.find(5)
2626 issue.status = IssueStatus.find(5)
2609 assert_equal true, issue.closing?
2627 assert_equal true, issue.closing?
2610 end
2628 end
2611
2629
2612 def test_closing_should_return_true_for_new_closed_issue_with_status_id
2630 def test_closing_should_return_true_for_new_closed_issue_with_status_id
2613 issue = Issue.new
2631 issue = Issue.new
2614 assert_equal false, issue.closing?
2632 assert_equal false, issue.closing?
2615 issue.status_id = 5
2633 issue.status_id = 5
2616 assert_equal true, issue.closing?
2634 assert_equal true, issue.closing?
2617 end
2635 end
2618
2636
2619 def test_closing_should_be_reset_after_save
2637 def test_closing_should_be_reset_after_save
2620 issue = Issue.find(1)
2638 issue = Issue.find(1)
2621 issue.status_id = 5
2639 issue.status_id = 5
2622 assert_equal true, issue.closing?
2640 assert_equal true, issue.closing?
2623 issue.save!
2641 issue.save!
2624 assert_equal false, issue.closing?
2642 assert_equal false, issue.closing?
2625 end
2643 end
2626
2644
2627 def test_reopening_should_return_true_when_reopening_an_issue
2645 def test_reopening_should_return_true_when_reopening_an_issue
2628 issue = Issue.find(8)
2646 issue = Issue.find(8)
2629 issue.status = IssueStatus.find(6)
2647 issue.status = IssueStatus.find(6)
2630 assert_equal false, issue.reopening?
2648 assert_equal false, issue.reopening?
2631 issue.status = IssueStatus.find(2)
2649 issue.status = IssueStatus.find(2)
2632 assert_equal true, issue.reopening?
2650 assert_equal true, issue.reopening?
2633 end
2651 end
2634
2652
2635 def test_reopening_should_return_true_when_reopening_an_issue_with_status_id
2653 def test_reopening_should_return_true_when_reopening_an_issue_with_status_id
2636 issue = Issue.find(8)
2654 issue = Issue.find(8)
2637 issue.status_id = 6
2655 issue.status_id = 6
2638 assert_equal false, issue.reopening?
2656 assert_equal false, issue.reopening?
2639 issue.status_id = 2
2657 issue.status_id = 2
2640 assert_equal true, issue.reopening?
2658 assert_equal true, issue.reopening?
2641 end
2659 end
2642
2660
2643 def test_reopening_should_return_false_for_new_open_issue
2661 def test_reopening_should_return_false_for_new_open_issue
2644 issue = Issue.new
2662 issue = Issue.new
2645 issue.status = IssueStatus.find(1)
2663 issue.status = IssueStatus.find(1)
2646 assert_equal false, issue.reopening?
2664 assert_equal false, issue.reopening?
2647 end
2665 end
2648
2666
2649 def test_reopening_should_be_reset_after_save
2667 def test_reopening_should_be_reset_after_save
2650 issue = Issue.find(8)
2668 issue = Issue.find(8)
2651 issue.status_id = 2
2669 issue.status_id = 2
2652 assert_equal true, issue.reopening?
2670 assert_equal true, issue.reopening?
2653 issue.save!
2671 issue.save!
2654 assert_equal false, issue.reopening?
2672 assert_equal false, issue.reopening?
2655 end
2673 end
2656
2674
2657 def test_default_status_without_tracker_should_be_nil
2675 def test_default_status_without_tracker_should_be_nil
2658 issue = Issue.new
2676 issue = Issue.new
2659 assert_nil issue.tracker
2677 assert_nil issue.tracker
2660 assert_nil issue.default_status
2678 assert_nil issue.default_status
2661 end
2679 end
2662
2680
2663 def test_default_status_should_be_tracker_default_status
2681 def test_default_status_should_be_tracker_default_status
2664 issue = Issue.new(:tracker_id => 1)
2682 issue = Issue.new(:tracker_id => 1)
2665 assert_not_nil issue.status
2683 assert_not_nil issue.status
2666 assert_equal issue.tracker.default_status, issue.default_status
2684 assert_equal issue.tracker.default_status, issue.default_status
2667 end
2685 end
2668
2686
2669 def test_initializing_with_tracker_should_set_default_status
2687 def test_initializing_with_tracker_should_set_default_status
2670 issue = Issue.new(:tracker => Tracker.find(1))
2688 issue = Issue.new(:tracker => Tracker.find(1))
2671 assert_not_nil issue.status
2689 assert_not_nil issue.status
2672 assert_equal issue.default_status, issue.status
2690 assert_equal issue.default_status, issue.status
2673 end
2691 end
2674
2692
2675 def test_initializing_with_tracker_id_should_set_default_status
2693 def test_initializing_with_tracker_id_should_set_default_status
2676 issue = Issue.new(:tracker_id => 1)
2694 issue = Issue.new(:tracker_id => 1)
2677 assert_not_nil issue.status
2695 assert_not_nil issue.status
2678 assert_equal issue.default_status, issue.status
2696 assert_equal issue.default_status, issue.status
2679 end
2697 end
2680
2698
2681 def test_setting_tracker_should_set_default_status
2699 def test_setting_tracker_should_set_default_status
2682 issue = Issue.new
2700 issue = Issue.new
2683 issue.tracker = Tracker.find(1)
2701 issue.tracker = Tracker.find(1)
2684 assert_not_nil issue.status
2702 assert_not_nil issue.status
2685 assert_equal issue.default_status, issue.status
2703 assert_equal issue.default_status, issue.status
2686 end
2704 end
2687
2705
2688 def test_changing_tracker_should_set_default_status_if_status_was_default
2706 def test_changing_tracker_should_set_default_status_if_status_was_default
2689 WorkflowTransition.delete_all
2707 WorkflowTransition.delete_all
2690 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1
2708 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1
2691 Tracker.find(2).update! :default_status_id => 2
2709 Tracker.find(2).update! :default_status_id => 2
2692
2710
2693 issue = Issue.new(:tracker_id => 1, :status_id => 1)
2711 issue = Issue.new(:tracker_id => 1, :status_id => 1)
2694 assert_equal IssueStatus.find(1), issue.status
2712 assert_equal IssueStatus.find(1), issue.status
2695 issue.tracker = Tracker.find(2)
2713 issue.tracker = Tracker.find(2)
2696 assert_equal IssueStatus.find(2), issue.status
2714 assert_equal IssueStatus.find(2), issue.status
2697 end
2715 end
2698
2716
2699 def test_changing_tracker_should_set_default_status_if_status_is_not_used_by_tracker
2717 def test_changing_tracker_should_set_default_status_if_status_is_not_used_by_tracker
2700 WorkflowTransition.delete_all
2718 WorkflowTransition.delete_all
2701 Tracker.find(2).update! :default_status_id => 2
2719 Tracker.find(2).update! :default_status_id => 2
2702
2720
2703 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2721 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2704 assert_equal IssueStatus.find(3), issue.status
2722 assert_equal IssueStatus.find(3), issue.status
2705 issue.tracker = Tracker.find(2)
2723 issue.tracker = Tracker.find(2)
2706 assert_equal IssueStatus.find(2), issue.status
2724 assert_equal IssueStatus.find(2), issue.status
2707 end
2725 end
2708
2726
2709 def test_changing_tracker_should_keep_status_if_status_was_not_default_and_is_used_by_tracker
2727 def test_changing_tracker_should_keep_status_if_status_was_not_default_and_is_used_by_tracker
2710 WorkflowTransition.delete_all
2728 WorkflowTransition.delete_all
2711 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3
2729 WorkflowTransition.create! :role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 3
2712 Tracker.find(2).update! :default_status_id => 2
2730 Tracker.find(2).update! :default_status_id => 2
2713
2731
2714 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2732 issue = Issue.new(:tracker_id => 1, :status_id => 3)
2715 assert_equal IssueStatus.find(3), issue.status
2733 assert_equal IssueStatus.find(3), issue.status
2716 issue.tracker = Tracker.find(2)
2734 issue.tracker = Tracker.find(2)
2717 assert_equal IssueStatus.find(3), issue.status
2735 assert_equal IssueStatus.find(3), issue.status
2718 end
2736 end
2719
2737
2720 def test_assigned_to_was_with_a_group
2738 def test_assigned_to_was_with_a_group
2721 group = Group.find(10)
2739 group = Group.find(10)
2722
2740
2723 issue = Issue.generate!(:assigned_to => group)
2741 issue = Issue.generate!(:assigned_to => group)
2724 issue.reload.assigned_to = nil
2742 issue.reload.assigned_to = nil
2725 assert_equal group, issue.assigned_to_was
2743 assert_equal group, issue.assigned_to_was
2726 end
2744 end
2727 end
2745 end
General Comments 0
You need to be logged in to leave comments. Login now