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