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