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