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