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