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